diff options
author | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
---|---|---|
committer | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
commit | a192cc2a132cb0ee8588e2df755563ec7008c179 (patch) | |
tree | 380e4db22df19c819bd37df34bf06e7568916a50 /android/support | |
parent | 98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff) | |
download | android-28-a192cc2a132cb0ee8588e2df755563ec7008c179.tar.gz |
Update fullsdk to 4575844
/google/data/ro/projects/android/fetch_artifact \
--bid 4575844 \
--target sdk_phone_x86_64-sdk \
sdk-repo-linux-sources-4575844.zip
Test: TreeHugger
Change-Id: I81e0eb157b4ac3b38408d0ef86f9d6286471f87a
Diffstat (limited to 'android/support')
137 files changed, 1900 insertions, 18903 deletions
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java index 6d8d6bf5..813d9a85 100644 --- a/android/support/LibraryVersions.java +++ b/android/support/LibraryVersions.java @@ -26,19 +26,14 @@ public class LibraryVersions { public static final Version SUPPORT_LIBRARY = new Version("28.0.0-SNAPSHOT"); /** - * Version code for flatfoot 1.0 projects (room, lifecycles) - */ - private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0"); - - /** * Version code for Room */ - public static final Version ROOM = FLATFOOT_1_0_BATCH; + public static final Version ROOM = new Version("1.1.0-alpha1"); /** * Version code for Lifecycle extensions (ProcessLifecycleOwner, Fragment support) */ - public static final Version LIFECYCLES_EXT = new Version("1.1.0-SNAPSHOT"); + public static final Version LIFECYCLES_EXT = new Version("1.1.0"); /** * Version code for Lifecycle LiveData @@ -53,9 +48,9 @@ public class LibraryVersions { /** * Version code for RecyclerView & Room paging */ - public static final Version PAGING = new Version("1.0.0-alpha4-1"); + public static final Version PAGING = new Version("1.0.0-alpha5"); - private static final Version LIFECYCLES = new Version("1.0.3"); + private static final Version LIFECYCLES = new Version("1.1.0"); /** * Version code for Lifecycle libs that are required by the support library @@ -70,15 +65,15 @@ public class LibraryVersions { /** * Version code for shared code of flatfoot */ - public static final Version ARCH_CORE = new Version("1.0.0"); + public static final Version ARCH_CORE = new Version("1.1.0"); /** * Version code for shared code of flatfoot runtime */ - public static final Version ARCH_RUNTIME = FLATFOOT_1_0_BATCH; + public static final Version ARCH_RUNTIME = ARCH_CORE; /** * Version code for shared testing code of flatfoot */ - public static final Version ARCH_CORE_TESTING = FLATFOOT_1_0_BATCH; + public static final Version ARCH_CORE_TESTING = ARCH_CORE; } diff --git a/android/support/Version.java b/android/support/Version.java deleted file mode 100644 index 36c7728b..00000000 --- a/android/support/Version.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import java.io.File; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Utility class which represents a version - */ -public class Version implements Comparable<Version> { - private static final Pattern VERSION_FILE_REGEX = Pattern.compile("^(\\d+\\.\\d+\\.\\d+).txt$"); - private static final Pattern VERSION_REGEX = Pattern - .compile("^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$"); - - private final int mMajor; - private final int mMinor; - private final int mPatch; - private final String mExtra; - - public Version(String versionString) { - this(checkedMatcher(versionString)); - } - - private static Matcher checkedMatcher(String versionString) { - Matcher matcher = VERSION_REGEX.matcher(versionString); - if (!matcher.matches()) { - throw new IllegalArgumentException("Can not parse version: " + versionString); - } - return matcher; - } - - private Version(Matcher matcher) { - mMajor = Integer.parseInt(matcher.group(1)); - mMinor = Integer.parseInt(matcher.group(2)); - mPatch = Integer.parseInt(matcher.group(3)); - mExtra = matcher.groupCount() == 4 ? matcher.group(4) : null; - } - - @Override - public int compareTo(Version version) { - if (mMajor != version.mMajor) { - return mMajor - version.mMajor; - } - if (mMinor != version.mMinor) { - return mMinor - version.mMinor; - } - if (mPatch != version.mPatch) { - return mPatch - version.mPatch; - } - if (mExtra == null) { - if (version.mExtra == null) { - return 0; - } - // not having any extra is always a later version - return 1; - } else { - if (version.mExtra == null) { - // not having any extra is always a later version - return -1; - } - // gradle uses lexicographic ordering - return mExtra.compareTo(version.mExtra); - } - } - - public boolean isPatch() { - return mPatch != 0; - } - - public boolean isSnapshot() { - return "-SNAPSHOT".equals(mExtra); - } - - public int getMajor() { - return mMajor; - } - - public int getMinor() { - return mMinor; - } - - public int getPatch() { - return mPatch; - } - - public String getExtra() { - return mExtra; - } - - @Override - public String toString() { - return mMajor + "." + mMinor + "." + mPatch + (mExtra != null ? mExtra : ""); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Version version = (Version) o; - - if (mMajor != version.mMajor) return false; - if (mMinor != version.mMinor) return false; - if (mPatch != version.mPatch) return false; - return mExtra != null ? mExtra.equals(version.mExtra) : version.mExtra == null; - } - - @Override - public int hashCode() { - int result = mMajor; - result = 31 * result + mMinor; - result = 31 * result + mPatch; - result = 31 * result + (mExtra != null ? mExtra.hashCode() : 0); - return result; - } - - /** - * @return Version or null, if a name of the given file doesn't match - */ - public static Version from(File file) { - if (!file.isFile()) { - return null; - } - Matcher matcher = VERSION_FILE_REGEX.matcher(file.getName()); - if (!matcher.matches()) { - return null; - } - return new Version(matcher.group(1)); - } - - /** - * @return Version or null, if the given string doesn't match - */ - public static Version from(String versionString) { - Matcher matcher = VERSION_REGEX.matcher(versionString); - if (!matcher.matches()) { - return null; - } - return new Version(matcher); - } -} diff --git a/android/support/VersionFileWriterTask.java b/android/support/VersionFileWriterTask.java deleted file mode 100644 index aafa0236..00000000 --- a/android/support/VersionFileWriterTask.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import com.android.build.gradle.LibraryExtension; - -import org.gradle.api.Action; -import org.gradle.api.DefaultTask; -import org.gradle.api.Project; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.TaskAction; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; - -/** - * Task that allows to write a version to a given output file. - */ -public class VersionFileWriterTask extends DefaultTask { - public static final String RESOURCE_DIRECTORY = "generatedResources"; - public static final String VERSION_FILE_PATH = - RESOURCE_DIRECTORY + "/META-INF/%s_%s.version"; - - private String mVersion; - private File mOutputFile; - - /** - * Sets up Android Library project to have a task that generates a version file. - * - * @param project an Android Library project. - */ - public static void setUpAndroidLibrary(Project project) { - project.afterEvaluate(new Action<Project>() { - @Override - public void execute(Project project) { - LibraryExtension library = - project.getExtensions().findByType(LibraryExtension.class); - - String group = (String) project.getProperties().get("group"); - String artifactId = (String) project.getProperties().get("name"); - String version = (String) project.getProperties().get("version"); - - // Add a java resource file to the library jar for version tracking purposes. - File artifactName = new File(project.getBuildDir(), - String.format(VersionFileWriterTask.VERSION_FILE_PATH, - group, artifactId)); - - VersionFileWriterTask writeVersionFile = - project.getTasks().create("writeVersionFile", VersionFileWriterTask.class); - writeVersionFile.setVersion(version); - writeVersionFile.setOutputFile(artifactName); - - library.getLibraryVariants().all( - libraryVariant -> libraryVariant.getProcessJavaResources().dependsOn( - writeVersionFile)); - - library.getSourceSets().getByName("main").getResources().srcDir( - new File(project.getBuildDir(), VersionFileWriterTask.RESOURCE_DIRECTORY) - ); - } - }); - } - - @Input - public String getVersion() { - return mVersion; - } - - public void setVersion(String version) { - mVersion = version; - } - - @OutputFile - public File getOutputFile() { - return mOutputFile; - } - - public void setOutputFile(File outputFile) { - mOutputFile = outputFile; - } - - /** - * The main method for actually writing out the file. - * - * @throws IOException - */ - @TaskAction - public void run() throws IOException { - PrintWriter writer = new PrintWriter(mOutputFile); - writer.println(mVersion); - writer.close(); - } -} diff --git a/android/support/animation/AnimationHandler.java b/android/support/animation/AnimationHandler.java index 6c39b23a..24bc43a2 100644 --- a/android/support/animation/AnimationHandler.java +++ b/android/support/animation/AnimationHandler.java @@ -35,8 +35,6 @@ import java.util.ArrayList; * The handler uses the Choreographer by default for doing periodic callbacks. A custom * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that * may be independent of UI frame update. This could be useful in testing. - * - * @hide */ class AnimationHandler { /** @@ -57,7 +55,7 @@ class AnimationHandler { * the new frame, so that they can update animation values as needed. */ class AnimationCallbackDispatcher { - public void dispatchAnimationFrame() { + void dispatchAnimationFrame() { mCurrentFrameTime = SystemClock.uptimeMillis(); AnimationHandler.this.doAnimationFrame(mCurrentFrameTime); if (mAnimationCallbacks.size() > 0) { @@ -72,7 +70,6 @@ class AnimationHandler { /** * Internal per-thread collections used to avoid set collisions as animations start and end * while being processed. - * @hide */ private final SimpleArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime = new SimpleArrayMap<>(); @@ -249,7 +246,7 @@ class AnimationHandler { * timing pulse without using Choreographer. That way we could use any arbitrary interval for * our timing pulse in the tests. */ - public abstract static class AnimationFrameCallbackProvider { + abstract static class AnimationFrameCallbackProvider { final AnimationCallbackDispatcher mDispatcher; AnimationFrameCallbackProvider(AnimationCallbackDispatcher dispatcher) { mDispatcher = dispatcher; diff --git a/android/support/animation/DynamicAnimation.java b/android/support/animation/DynamicAnimation.java index 8ea48b94..7cbd5bb2 100644 --- a/android/support/animation/DynamicAnimation.java +++ b/android/support/animation/DynamicAnimation.java @@ -18,6 +18,7 @@ package android.support.animation; import android.os.Looper; import android.support.annotation.FloatRange; +import android.support.annotation.RestrictTo; import android.support.v4.view.ViewCompat; import android.util.AndroidRuntimeException; import android.view.View; @@ -631,6 +632,7 @@ public abstract class DynamicAnimation<T extends DynamicAnimation<T>> * * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) @Override public boolean doAnimationFrame(long frameTime) { if (mLastFrameTime == 0) { diff --git a/android/support/animation/SpringForce.java b/android/support/animation/SpringForce.java index 5f95aa8c..dfb4c674 100644 --- a/android/support/animation/SpringForce.java +++ b/android/support/animation/SpringForce.java @@ -17,6 +17,7 @@ package android.support.animation; import android.support.annotation.FloatRange; +import android.support.annotation.RestrictTo; /** * Spring Force defines the characteristics of the spring being used in the animation. @@ -210,6 +211,7 @@ public final class SpringForce implements Force { /** * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) @Override public float getAcceleration(float lastDisplacement, float lastVelocity) { @@ -224,6 +226,7 @@ public final class SpringForce implements Force { /** * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) @Override public boolean isAtEquilibrium(float value, float velocity) { if (Math.abs(velocity) < mVelocityThreshold diff --git a/android/support/customtabs/CustomTabsClient.java b/android/support/customtabs/CustomTabsClient.java index 2e955cbe..371b5a1f 100644 --- a/android/support/customtabs/CustomTabsClient.java +++ b/android/support/customtabs/CustomTabsClient.java @@ -45,7 +45,7 @@ public class CustomTabsClient { private final ICustomTabsService mService; private final ComponentName mServiceComponentName; - /**@hide*/ + /** @hide */ @RestrictTo(LIBRARY_GROUP) CustomTabsClient(ICustomTabsService service, ComponentName componentName) { mService = service; diff --git a/android/support/design/internal/BaselineLayout.java b/android/support/design/internal/BaselineLayout.java deleted file mode 100644 index 0bfdf249..00000000 --- a/android/support/design/internal/BaselineLayout.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2016 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.internal; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; - -/** - * A simple ViewGroup that aligns all the views inside on a baseline. Note: bottom padding for this - * view will be measured starting from the baseline. - * - * @hide - */ -public class BaselineLayout extends ViewGroup { - private int mBaseline = -1; - - public BaselineLayout(Context context) { - super(context, null, 0); - } - - public BaselineLayout(Context context, AttributeSet attrs) { - super(context, attrs, 0); - } - - public BaselineLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int count = getChildCount(); - int maxWidth = 0; - int maxHeight = 0; - int maxChildBaseline = -1; - int maxChildDescent = -1; - int childState = 0; - - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - measureChild(child, widthMeasureSpec, heightMeasureSpec); - final int baseline = child.getBaseline(); - if (baseline != -1) { - maxChildBaseline = Math.max(maxChildBaseline, baseline); - maxChildDescent = Math.max(maxChildDescent, child.getMeasuredHeight() - baseline); - } - maxWidth = Math.max(maxWidth, child.getMeasuredWidth()); - maxHeight = Math.max(maxHeight, child.getMeasuredHeight()); - childState = View.combineMeasuredStates(childState, child.getMeasuredState()); - } - if (maxChildBaseline != -1) { - maxChildDescent = Math.max(maxChildDescent, getPaddingBottom()); - maxHeight = Math.max(maxHeight, maxChildBaseline + maxChildDescent); - mBaseline = maxChildBaseline; - } - maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); - maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); - setMeasuredDimension( - View.resolveSizeAndState(maxWidth, widthMeasureSpec, childState), - View.resolveSizeAndState(maxHeight, heightMeasureSpec, - childState << MEASURED_HEIGHT_STATE_SHIFT)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - final int count = getChildCount(); - final int parentLeft = getPaddingLeft(); - final int parentRight = right - left - getPaddingRight(); - final int parentContentWidth = parentRight - parentLeft; - final int parentTop = getPaddingTop(); - - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - final int width = child.getMeasuredWidth(); - final int height = child.getMeasuredHeight(); - - final int childLeft = parentLeft + (parentContentWidth - width) / 2; - final int childTop; - if (mBaseline != -1 && child.getBaseline() != -1) { - childTop = parentTop + mBaseline - child.getBaseline(); - } else { - childTop = parentTop; - } - - child.layout(childLeft, childTop, childLeft + width, childTop + height); - } - } - - @Override - public int getBaseline() { - return mBaseline; - } -} diff --git a/android/support/design/internal/BottomNavigationItemView.java b/android/support/design/internal/BottomNavigationItemView.java deleted file mode 100644 index fe5e636f..00000000 --- a/android/support/design/internal/BottomNavigationItemView.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2016 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.design.R; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.view.PointerIconCompat; -import android.support.v4.view.ViewCompat; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.view.menu.MenuView; -import android.support.v7.widget.TooltipCompat; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView { - public static final int INVALID_ITEM_POSITION = -1; - - private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked }; - - private final int mDefaultMargin; - private final int mShiftAmount; - private final float mScaleUpFactor; - private final float mScaleDownFactor; - - private boolean mShiftingMode; - - private ImageView mIcon; - private final TextView mSmallLabel; - private final TextView mLargeLabel; - private int mItemPosition = INVALID_ITEM_POSITION; - - private MenuItemImpl mItemData; - - private ColorStateList mIconTint; - - public BottomNavigationItemView(@NonNull Context context) { - this(context, null); - } - - public BottomNavigationItemView(@NonNull Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - final Resources res = getResources(); - int inactiveLabelSize = - res.getDimensionPixelSize(R.dimen.design_bottom_navigation_text_size); - int activeLabelSize = res.getDimensionPixelSize( - R.dimen.design_bottom_navigation_active_text_size); - mDefaultMargin = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_margin); - mShiftAmount = inactiveLabelSize - activeLabelSize; - mScaleUpFactor = 1f * activeLabelSize / inactiveLabelSize; - mScaleDownFactor = 1f * inactiveLabelSize / activeLabelSize; - - LayoutInflater.from(context).inflate(R.layout.design_bottom_navigation_item, this, true); - setBackgroundResource(R.drawable.design_bottom_navigation_item_background); - mIcon = findViewById(R.id.icon); - mSmallLabel = findViewById(R.id.smallLabel); - mLargeLabel = findViewById(R.id.largeLabel); - } - - @Override - public void initialize(MenuItemImpl itemData, int menuType) { - mItemData = itemData; - setCheckable(itemData.isCheckable()); - setChecked(itemData.isChecked()); - setEnabled(itemData.isEnabled()); - setIcon(itemData.getIcon()); - setTitle(itemData.getTitle()); - setId(itemData.getItemId()); - setContentDescription(itemData.getContentDescription()); - TooltipCompat.setTooltipText(this, itemData.getTooltipText()); - } - - public void setItemPosition(int position) { - mItemPosition = position; - } - - public int getItemPosition() { - return mItemPosition; - } - - public void setShiftingMode(boolean enabled) { - mShiftingMode = enabled; - } - - @Override - public MenuItemImpl getItemData() { - return mItemData; - } - - @Override - public void setTitle(CharSequence title) { - mSmallLabel.setText(title); - mLargeLabel.setText(title); - } - - @Override - public void setCheckable(boolean checkable) { - refreshDrawableState(); - } - - @Override - public void setChecked(boolean checked) { - mLargeLabel.setPivotX(mLargeLabel.getWidth() / 2); - mLargeLabel.setPivotY(mLargeLabel.getBaseline()); - mSmallLabel.setPivotX(mSmallLabel.getWidth() / 2); - mSmallLabel.setPivotY(mSmallLabel.getBaseline()); - if (mShiftingMode) { - if (checked) { - LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams(); - iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - iconParams.topMargin = mDefaultMargin; - mIcon.setLayoutParams(iconParams); - mLargeLabel.setVisibility(VISIBLE); - mLargeLabel.setScaleX(1f); - mLargeLabel.setScaleY(1f); - } else { - LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams(); - iconParams.gravity = Gravity.CENTER; - iconParams.topMargin = mDefaultMargin; - mIcon.setLayoutParams(iconParams); - mLargeLabel.setVisibility(INVISIBLE); - mLargeLabel.setScaleX(0.5f); - mLargeLabel.setScaleY(0.5f); - } - mSmallLabel.setVisibility(INVISIBLE); - } else { - if (checked) { - LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams(); - iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - iconParams.topMargin = mDefaultMargin + mShiftAmount; - mIcon.setLayoutParams(iconParams); - mLargeLabel.setVisibility(VISIBLE); - mSmallLabel.setVisibility(INVISIBLE); - - mLargeLabel.setScaleX(1f); - mLargeLabel.setScaleY(1f); - mSmallLabel.setScaleX(mScaleUpFactor); - mSmallLabel.setScaleY(mScaleUpFactor); - } else { - LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams(); - iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - iconParams.topMargin = mDefaultMargin; - mIcon.setLayoutParams(iconParams); - mLargeLabel.setVisibility(INVISIBLE); - mSmallLabel.setVisibility(VISIBLE); - - mLargeLabel.setScaleX(mScaleDownFactor); - mLargeLabel.setScaleY(mScaleDownFactor); - mSmallLabel.setScaleX(1f); - mSmallLabel.setScaleY(1f); - } - } - - refreshDrawableState(); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - mSmallLabel.setEnabled(enabled); - mLargeLabel.setEnabled(enabled); - mIcon.setEnabled(enabled); - - if (enabled) { - ViewCompat.setPointerIcon(this, - PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND)); - } else { - ViewCompat.setPointerIcon(this, null); - } - - } - - @Override - public int[] onCreateDrawableState(final int extraSpace) { - final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); - if (mItemData != null && mItemData.isCheckable() && mItemData.isChecked()) { - mergeDrawableStates(drawableState, CHECKED_STATE_SET); - } - return drawableState; - } - - @Override - public void setShortcut(boolean showShortcut, char shortcutKey) { - } - - @Override - public void setIcon(Drawable icon) { - if (icon != null) { - Drawable.ConstantState state = icon.getConstantState(); - icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate(); - DrawableCompat.setTintList(icon, mIconTint); - } - mIcon.setImageDrawable(icon); - } - - @Override - public boolean prefersCondensedTitle() { - return false; - } - - @Override - public boolean showsIcon() { - return true; - } - - public void setIconTintList(ColorStateList tint) { - mIconTint = tint; - if (mItemData != null) { - // Update the icon so that the tint takes effect - setIcon(mItemData.getIcon()); - } - } - - public void setTextColor(ColorStateList color) { - mSmallLabel.setTextColor(color); - mLargeLabel.setTextColor(color); - } - - public void setItemBackground(int background) { - Drawable backgroundDrawable = background == 0 - ? null : ContextCompat.getDrawable(getContext(), background); - ViewCompat.setBackground(this, backgroundDrawable); - } -} diff --git a/android/support/design/internal/BottomNavigationMenu.java b/android/support/design/internal/BottomNavigationMenu.java deleted file mode 100644 index a86d2adf..00000000 --- a/android/support/design/internal/BottomNavigationMenu.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.support.annotation.RestrictTo; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuItemImpl; -import android.view.MenuItem; -import android.view.SubMenu; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public final class BottomNavigationMenu extends MenuBuilder { - public static final int MAX_ITEM_COUNT = 5; - - public BottomNavigationMenu(Context context) { - super(context); - } - - @Override - public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) { - throw new UnsupportedOperationException("BottomNavigationView does not support submenus"); - } - - @Override - protected MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) { - if (size() + 1 > MAX_ITEM_COUNT) { - throw new IllegalArgumentException( - "Maximum number of items supported by BottomNavigationView is " + MAX_ITEM_COUNT - + ". Limit can be checked with BottomNavigationView#getMaxItemCount()"); - } - stopDispatchingItemsChanged(); - final MenuItem item = super.addInternal(group, id, categoryOrder, title); - if (item instanceof MenuItemImpl) { - ((MenuItemImpl) item).setExclusiveCheckable(true); - } - startDispatchingItemsChanged(); - return item; - } -} diff --git a/android/support/design/internal/BottomNavigationMenuView.java b/android/support/design/internal/BottomNavigationMenuView.java deleted file mode 100644 index bf33454e..00000000 --- a/android/support/design/internal/BottomNavigationMenuView.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (C) 2016 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.design.R; -import android.support.transition.AutoTransition; -import android.support.transition.TransitionManager; -import android.support.transition.TransitionSet; -import android.support.v4.util.Pools; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.animation.FastOutSlowInInterpolator; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.view.menu.MenuView; -import android.util.AttributeSet; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -/** - * @hide For internal use only. - */ -@RestrictTo(LIBRARY_GROUP) -public class BottomNavigationMenuView extends ViewGroup implements MenuView { - private static final long ACTIVE_ANIMATION_DURATION_MS = 115L; - - private final TransitionSet mSet; - private final int mInactiveItemMaxWidth; - private final int mInactiveItemMinWidth; - private final int mActiveItemMaxWidth; - private final int mItemHeight; - private final OnClickListener mOnClickListener; - private final Pools.Pool<BottomNavigationItemView> mItemPool = new Pools.SynchronizedPool<>(5); - - private boolean mShiftingMode = true; - - private BottomNavigationItemView[] mButtons; - private int mSelectedItemId = 0; - private int mSelectedItemPosition = 0; - private ColorStateList mItemIconTint; - private ColorStateList mItemTextColor; - private int mItemBackgroundRes; - private int[] mTempChildWidths; - - private BottomNavigationPresenter mPresenter; - private MenuBuilder mMenu; - - public BottomNavigationMenuView(Context context) { - this(context, null); - } - - public BottomNavigationMenuView(Context context, AttributeSet attrs) { - super(context, attrs); - final Resources res = getResources(); - mInactiveItemMaxWidth = res.getDimensionPixelSize( - R.dimen.design_bottom_navigation_item_max_width); - mInactiveItemMinWidth = res.getDimensionPixelSize( - R.dimen.design_bottom_navigation_item_min_width); - mActiveItemMaxWidth = res.getDimensionPixelSize( - R.dimen.design_bottom_navigation_active_item_max_width); - mItemHeight = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_height); - - mSet = new AutoTransition(); - mSet.setOrdering(TransitionSet.ORDERING_TOGETHER); - mSet.setDuration(ACTIVE_ANIMATION_DURATION_MS); - mSet.setInterpolator(new FastOutSlowInInterpolator()); - mSet.addTransition(new TextScale()); - - mOnClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - final BottomNavigationItemView itemView = (BottomNavigationItemView) v; - MenuItem item = itemView.getItemData(); - if (!mMenu.performItemAction(item, mPresenter, 0)) { - item.setChecked(true); - } - } - }; - mTempChildWidths = new int[BottomNavigationMenu.MAX_ITEM_COUNT]; - } - - @Override - public void initialize(MenuBuilder menu) { - mMenu = menu; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int width = MeasureSpec.getSize(widthMeasureSpec); - final int count = getChildCount(); - - final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY); - - if (mShiftingMode) { - final int inactiveCount = count - 1; - final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth; - final int activeWidth = Math.min(activeMaxAvailable, mActiveItemMaxWidth); - final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount; - final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth); - int extra = width - activeWidth - inactiveWidth * inactiveCount; - for (int i = 0; i < count; i++) { - mTempChildWidths[i] = (i == mSelectedItemPosition) ? activeWidth : inactiveWidth; - if (extra > 0) { - mTempChildWidths[i]++; - extra--; - } - } - } else { - final int maxAvailable = width / (count == 0 ? 1 : count); - final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth); - int extra = width - childWidth * count; - for (int i = 0; i < count; i++) { - mTempChildWidths[i] = childWidth; - if (extra > 0) { - mTempChildWidths[i]++; - extra--; - } - } - } - - int totalWidth = 0; - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY), - heightSpec); - ViewGroup.LayoutParams params = child.getLayoutParams(); - params.width = child.getMeasuredWidth(); - totalWidth += child.getMeasuredWidth(); - } - setMeasuredDimension( - View.resolveSizeAndState(totalWidth, - MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), 0), - View.resolveSizeAndState(mItemHeight, heightSpec, 0)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - final int count = getChildCount(); - final int width = right - left; - final int height = bottom - top; - int used = 0; - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) { - child.layout(width - used - child.getMeasuredWidth(), 0, width - used, height); - } else { - child.layout(used, 0, child.getMeasuredWidth() + used, height); - } - used += child.getMeasuredWidth(); - } - } - - @Override - public int getWindowAnimations() { - return 0; - } - - /** - * Sets the tint which is applied to the menu items' icons. - * - * @param tint the tint to apply - */ - public void setIconTintList(ColorStateList tint) { - mItemIconTint = tint; - if (mButtons == null) return; - for (BottomNavigationItemView item : mButtons) { - item.setIconTintList(tint); - } - } - - /** - * Returns the tint which is applied to menu items' icons. - * - * @return the ColorStateList that is used to tint menu items' icons - */ - @Nullable - public ColorStateList getIconTintList() { - return mItemIconTint; - } - - /** - * Sets the text color to be used on menu items. - * - * @param color the ColorStateList used for menu items' text. - */ - public void setItemTextColor(ColorStateList color) { - mItemTextColor = color; - if (mButtons == null) return; - for (BottomNavigationItemView item : mButtons) { - item.setTextColor(color); - } - } - - /** - * Returns the text color used on menu items. - * - * @return the ColorStateList used for menu items' text - */ - public ColorStateList getItemTextColor() { - return mItemTextColor; - } - - /** - * Sets the resource ID to be used for item background. - * - * @param background the resource ID of the background - */ - public void setItemBackgroundRes(int background) { - mItemBackgroundRes = background; - if (mButtons == null) return; - for (BottomNavigationItemView item : mButtons) { - item.setItemBackground(background); - } - } - - /** - * Returns the resource ID for the background of the menu items. - * - * @return the resource ID for the background - */ - public int getItemBackgroundRes() { - return mItemBackgroundRes; - } - - public void setPresenter(BottomNavigationPresenter presenter) { - mPresenter = presenter; - } - - public void buildMenuView() { - removeAllViews(); - if (mButtons != null) { - for (BottomNavigationItemView item : mButtons) { - mItemPool.release(item); - } - } - if (mMenu.size() == 0) { - mSelectedItemId = 0; - mSelectedItemPosition = 0; - mButtons = null; - return; - } - mButtons = new BottomNavigationItemView[mMenu.size()]; - mShiftingMode = mMenu.size() > 3; - for (int i = 0; i < mMenu.size(); i++) { - mPresenter.setUpdateSuspended(true); - mMenu.getItem(i).setCheckable(true); - mPresenter.setUpdateSuspended(false); - BottomNavigationItemView child = getNewItem(); - mButtons[i] = child; - child.setIconTintList(mItemIconTint); - child.setTextColor(mItemTextColor); - child.setItemBackground(mItemBackgroundRes); - child.setShiftingMode(mShiftingMode); - child.initialize((MenuItemImpl) mMenu.getItem(i), 0); - child.setItemPosition(i); - child.setOnClickListener(mOnClickListener); - addView(child); - } - mSelectedItemPosition = Math.min(mMenu.size() - 1, mSelectedItemPosition); - mMenu.getItem(mSelectedItemPosition).setChecked(true); - } - - public void updateMenuView() { - final int menuSize = mMenu.size(); - if (menuSize != mButtons.length) { - // The size has changed. Rebuild menu view from scratch. - buildMenuView(); - return; - } - int previousSelectedId = mSelectedItemId; - - for (int i = 0; i < menuSize; i++) { - MenuItem item = mMenu.getItem(i); - if (item.isChecked()) { - mSelectedItemId = item.getItemId(); - mSelectedItemPosition = i; - } - } - if (previousSelectedId != mSelectedItemId) { - // Note: this has to be called before BottomNavigationItemView#initialize(). - TransitionManager.beginDelayedTransition(this, mSet); - } - - for (int i = 0; i < menuSize; i++) { - mPresenter.setUpdateSuspended(true); - mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0); - mPresenter.setUpdateSuspended(false); - } - - } - - private BottomNavigationItemView getNewItem() { - BottomNavigationItemView item = mItemPool.acquire(); - if (item == null) { - item = new BottomNavigationItemView(getContext()); - } - return item; - } - - public int getSelectedItemId() { - return mSelectedItemId; - } - - void tryRestoreSelectedItemId(int itemId) { - final int size = mMenu.size(); - for (int i = 0; i < size; i++) { - MenuItem item = mMenu.getItem(i); - if (itemId == item.getItemId()) { - mSelectedItemId = itemId; - mSelectedItemPosition = i; - item.setChecked(true); - break; - } - } - } -} diff --git a/android/support/design/internal/BottomNavigationPresenter.java b/android/support/design/internal/BottomNavigationPresenter.java deleted file mode 100644 index 1343a4bf..00000000 --- a/android/support/design/internal/BottomNavigationPresenter.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2016 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.view.menu.MenuPresenter; -import android.support.v7.view.menu.MenuView; -import android.support.v7.view.menu.SubMenuBuilder; -import android.view.ViewGroup; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class BottomNavigationPresenter implements MenuPresenter { - private MenuBuilder mMenu; - private BottomNavigationMenuView mMenuView; - private boolean mUpdateSuspended = false; - private int mId; - - public void setBottomNavigationMenuView(BottomNavigationMenuView menuView) { - mMenuView = menuView; - } - - @Override - public void initForMenu(Context context, MenuBuilder menu) { - mMenuView.initialize(mMenu); - mMenu = menu; - } - - @Override - public MenuView getMenuView(ViewGroup root) { - return mMenuView; - } - - @Override - public void updateMenuView(boolean cleared) { - if (mUpdateSuspended) return; - if (cleared) { - mMenuView.buildMenuView(); - } else { - mMenuView.updateMenuView(); - } - } - - @Override - public void setCallback(Callback cb) {} - - @Override - public boolean onSubMenuSelected(SubMenuBuilder subMenu) { - return false; - } - - @Override - public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {} - - @Override - public boolean flagActionItems() { - return false; - } - - @Override - public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - @Override - public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - public void setId(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } - - @Override - public Parcelable onSaveInstanceState() { - SavedState savedState = new SavedState(); - savedState.selectedItemId = mMenuView.getSelectedItemId(); - return savedState; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof SavedState) { - mMenuView.tryRestoreSelectedItemId(((SavedState) state).selectedItemId); - } - } - - public void setUpdateSuspended(boolean updateSuspended) { - mUpdateSuspended = updateSuspended; - } - - static class SavedState implements Parcelable { - int selectedItemId; - - SavedState() {} - - SavedState(Parcel in) { - selectedItemId = in.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(selectedItemId); - } - - public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } -} diff --git a/android/support/design/internal/ForegroundLinearLayout.java b/android/support/design/internal/ForegroundLinearLayout.java deleted file mode 100644 index 6d905038..00000000 --- a/android/support/design/internal/ForegroundLinearLayout.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.RequiresApi; -import android.support.annotation.RestrictTo; -import android.support.design.R; -import android.support.v7.widget.LinearLayoutCompat; -import android.util.AttributeSet; -import android.view.Gravity; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class ForegroundLinearLayout extends LinearLayoutCompat { - - private Drawable mForeground; - - private final Rect mSelfBounds = new Rect(); - - private final Rect mOverlayBounds = new Rect(); - - private int mForegroundGravity = Gravity.FILL; - - protected boolean mForegroundInPadding = true; - - boolean mForegroundBoundsChanged = false; - - public ForegroundLinearLayout(Context context) { - this(context, null); - } - - public ForegroundLinearLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ForegroundLinearLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundLinearLayout, - defStyle, 0); - - mForegroundGravity = a.getInt( - R.styleable.ForegroundLinearLayout_android_foregroundGravity, mForegroundGravity); - - final Drawable d = a.getDrawable(R.styleable.ForegroundLinearLayout_android_foreground); - if (d != null) { - setForeground(d); - } - - mForegroundInPadding = a.getBoolean( - R.styleable.ForegroundLinearLayout_foregroundInsidePadding, true); - - a.recycle(); - } - - /** - * Describes how the foreground is positioned. - * - * @return foreground gravity. - * @see #setForegroundGravity(int) - */ - @Override - public int getForegroundGravity() { - return mForegroundGravity; - } - - /** - * Describes how the foreground is positioned. Defaults to START and TOP. - * - * @param foregroundGravity See {@link android.view.Gravity} - * @see #getForegroundGravity() - */ - @Override - public void setForegroundGravity(int foregroundGravity) { - if (mForegroundGravity != foregroundGravity) { - if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.START; - } - - if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.TOP; - } - - mForegroundGravity = foregroundGravity; - - if (mForegroundGravity == Gravity.FILL && mForeground != null) { - Rect padding = new Rect(); - mForeground.getPadding(padding); - } - - requestLayout(); - } - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || (who == mForeground); - } - - @RequiresApi(11) - @Override - public void jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState(); - if (mForeground != null) { - mForeground.jumpToCurrentState(); - } - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (mForeground != null && mForeground.isStateful()) { - mForeground.setState(getDrawableState()); - } - } - - /** - * Supply a Drawable that is to be rendered on top of all of the child - * views in the frame layout. Any padding in the Drawable will be taken - * into account by ensuring that the children are inset to be placed - * inside of the padding area. - * - * @param drawable The Drawable to be drawn on top of the children. - */ - @Override - public void setForeground(Drawable drawable) { - if (mForeground != drawable) { - if (mForeground != null) { - mForeground.setCallback(null); - unscheduleDrawable(mForeground); - } - - mForeground = drawable; - - if (drawable != null) { - setWillNotDraw(false); - drawable.setCallback(this); - if (drawable.isStateful()) { - drawable.setState(getDrawableState()); - } - if (mForegroundGravity == Gravity.FILL) { - Rect padding = new Rect(); - drawable.getPadding(padding); - } - } else { - setWillNotDraw(true); - } - requestLayout(); - invalidate(); - } - } - - /** - * Returns the drawable used as the foreground of this FrameLayout. The - * foreground drawable, if non-null, is always drawn on top of the children. - * - * @return A Drawable or null if no foreground was set. - */ - @Override - public Drawable getForeground() { - return mForeground; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mForegroundBoundsChanged |= changed; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mForegroundBoundsChanged = true; - } - - @Override - public void draw(@NonNull Canvas canvas) { - super.draw(canvas); - - if (mForeground != null) { - final Drawable foreground = mForeground; - - if (mForegroundBoundsChanged) { - mForegroundBoundsChanged = false; - final Rect selfBounds = mSelfBounds; - final Rect overlayBounds = mOverlayBounds; - - final int w = getRight() - getLeft(); - final int h = getBottom() - getTop(); - - if (mForegroundInPadding) { - selfBounds.set(0, 0, w, h); - } else { - selfBounds.set(getPaddingLeft(), getPaddingTop(), - w - getPaddingRight(), h - getPaddingBottom()); - } - - Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), - foreground.getIntrinsicHeight(), selfBounds, overlayBounds); - foreground.setBounds(overlayBounds); - } - - foreground.draw(canvas); - } - } - - @RequiresApi(21) - @Override - public void drawableHotspotChanged(float x, float y) { - super.drawableHotspotChanged(x, y); - if (mForeground != null) { - mForeground.setHotspot(x, y); - } - } - -} diff --git a/android/support/design/internal/NavigationMenu.java b/android/support/design/internal/NavigationMenu.java deleted file mode 100644 index a0ec5e0d..00000000 --- a/android/support/design/internal/NavigationMenu.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.support.annotation.RestrictTo; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.view.menu.SubMenuBuilder; -import android.view.SubMenu; - -/** - * This is a {@link MenuBuilder} that returns an instance of {@link NavigationSubMenu} instead of - * {@link SubMenuBuilder} when a sub menu is created. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class NavigationMenu extends MenuBuilder { - - public NavigationMenu(Context context) { - super(context); - } - - @Override - public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) { - final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title); - final SubMenuBuilder subMenu = new NavigationSubMenu(getContext(), this, item); - item.setSubMenu(subMenu); - return subMenu; - } - -} diff --git a/android/support/design/internal/NavigationMenuItemView.java b/android/support/design/internal/NavigationMenuItemView.java deleted file mode 100644 index eea9e90f..00000000 --- a/android/support/design/internal/NavigationMenuItemView.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; -import android.support.annotation.RestrictTo; -import android.support.design.R; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v4.widget.TextViewCompat; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.view.menu.MenuView; -import android.support.v7.widget.TooltipCompat; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewStub; -import android.widget.CheckedTextView; -import android.widget.FrameLayout; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class NavigationMenuItemView extends ForegroundLinearLayout implements MenuView.ItemView { - - private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; - - private final int mIconSize; - - private boolean mNeedsEmptyIcon; - - boolean mCheckable; - - private final CheckedTextView mTextView; - - private FrameLayout mActionArea; - - private MenuItemImpl mItemData; - - private ColorStateList mIconTintList; - - private boolean mHasIconTintList; - - private Drawable mEmptyDrawable; - - private final AccessibilityDelegateCompat mAccessibilityDelegate - = new AccessibilityDelegateCompat() { - - @Override - public void onInitializeAccessibilityNodeInfo(View host, - AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setCheckable(mCheckable); - } - - }; - - public NavigationMenuItemView(Context context) { - this(context, null); - } - - public NavigationMenuItemView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public NavigationMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setOrientation(HORIZONTAL); - LayoutInflater.from(context).inflate(R.layout.design_navigation_menu_item, this, true); - mIconSize = context.getResources().getDimensionPixelSize( - R.dimen.design_navigation_icon_size); - mTextView = findViewById(R.id.design_menu_item_text); - mTextView.setDuplicateParentStateEnabled(true); - ViewCompat.setAccessibilityDelegate(mTextView, mAccessibilityDelegate); - } - - @Override - public void initialize(MenuItemImpl itemData, int menuType) { - mItemData = itemData; - - setVisibility(itemData.isVisible() ? VISIBLE : GONE); - - if (getBackground() == null) { - ViewCompat.setBackground(this, createDefaultBackground()); - } - - setCheckable(itemData.isCheckable()); - setChecked(itemData.isChecked()); - setEnabled(itemData.isEnabled()); - setTitle(itemData.getTitle()); - setIcon(itemData.getIcon()); - setActionView(itemData.getActionView()); - setContentDescription(itemData.getContentDescription()); - TooltipCompat.setTooltipText(this, itemData.getTooltipText()); - adjustAppearance(); - } - - private boolean shouldExpandActionArea() { - return mItemData.getTitle() == null && - mItemData.getIcon() == null && - mItemData.getActionView() != null; - } - - private void adjustAppearance() { - if (shouldExpandActionArea()) { - // Expand the actionView area - mTextView.setVisibility(View.GONE); - if (mActionArea != null) { - LayoutParams params = (LayoutParams) mActionArea.getLayoutParams(); - params.width = LayoutParams.MATCH_PARENT; - mActionArea.setLayoutParams(params); - } - } else { - mTextView.setVisibility(View.VISIBLE); - if (mActionArea != null) { - LayoutParams params = (LayoutParams) mActionArea.getLayoutParams(); - params.width = LayoutParams.WRAP_CONTENT; - mActionArea.setLayoutParams(params); - } - } - } - - public void recycle() { - if (mActionArea != null) { - mActionArea.removeAllViews(); - } - mTextView.setCompoundDrawables(null, null, null, null); - } - - private void setActionView(View actionView) { - if (actionView != null) { - if (mActionArea == null) { - mActionArea = (FrameLayout) ((ViewStub) findViewById( - R.id.design_menu_item_action_area_stub)).inflate(); - } - mActionArea.removeAllViews(); - mActionArea.addView(actionView); - } - } - - private StateListDrawable createDefaultBackground() { - TypedValue value = new TypedValue(); - if (getContext().getTheme().resolveAttribute( - android.support.v7.appcompat.R.attr.colorControlHighlight, value, true)) { - StateListDrawable drawable = new StateListDrawable(); - drawable.addState(CHECKED_STATE_SET, new ColorDrawable(value.data)); - drawable.addState(EMPTY_STATE_SET, new ColorDrawable(Color.TRANSPARENT)); - return drawable; - } - return null; - } - - @Override - public MenuItemImpl getItemData() { - return mItemData; - } - - @Override - public void setTitle(CharSequence title) { - mTextView.setText(title); - } - - @Override - public void setCheckable(boolean checkable) { - refreshDrawableState(); - if (mCheckable != checkable) { - mCheckable = checkable; - mAccessibilityDelegate.sendAccessibilityEvent(mTextView, - AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED); - } - } - - @Override - public void setChecked(boolean checked) { - refreshDrawableState(); - mTextView.setChecked(checked); - } - - @Override - public void setShortcut(boolean showShortcut, char shortcutKey) { - } - - @Override - public void setIcon(Drawable icon) { - if (icon != null) { - if (mHasIconTintList) { - Drawable.ConstantState state = icon.getConstantState(); - icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate(); - DrawableCompat.setTintList(icon, mIconTintList); - } - icon.setBounds(0, 0, mIconSize, mIconSize); - } else if (mNeedsEmptyIcon) { - if (mEmptyDrawable == null) { - mEmptyDrawable = ResourcesCompat.getDrawable(getResources(), - R.drawable.navigation_empty_icon, getContext().getTheme()); - if (mEmptyDrawable != null) { - mEmptyDrawable.setBounds(0, 0, mIconSize, mIconSize); - } - } - icon = mEmptyDrawable; - } - TextViewCompat.setCompoundDrawablesRelative(mTextView, icon, null, null, null); - } - - @Override - public boolean prefersCondensedTitle() { - return false; - } - - @Override - public boolean showsIcon() { - return true; - } - - @Override - protected int[] onCreateDrawableState(int extraSpace) { - final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); - if (mItemData != null && mItemData.isCheckable() && mItemData.isChecked()) { - mergeDrawableStates(drawableState, CHECKED_STATE_SET); - } - return drawableState; - } - - void setIconTintList(ColorStateList tintList) { - mIconTintList = tintList; - mHasIconTintList = mIconTintList != null; - if (mItemData != null) { - // Update the icon so that the tint takes effect - setIcon(mItemData.getIcon()); - } - } - - public void setTextAppearance(int textAppearance) { - TextViewCompat.setTextAppearance(mTextView, textAppearance); - } - - public void setTextColor(ColorStateList colors) { - mTextView.setTextColor(colors); - } - - public void setNeedsEmptyIcon(boolean needsEmptyIcon) { - mNeedsEmptyIcon = needsEmptyIcon; - } - -} diff --git a/android/support/design/internal/NavigationMenuPresenter.java b/android/support/design/internal/NavigationMenuPresenter.java deleted file mode 100644 index 98ad4688..00000000 --- a/android/support/design/internal/NavigationMenuPresenter.java +++ /dev/null @@ -1,686 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcelable; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.StyleRes; -import android.support.design.R; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.WindowInsetsCompat; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.view.menu.MenuPresenter; -import android.support.v7.view.menu.MenuView; -import android.support.v7.view.menu.SubMenuBuilder; -import android.support.v7.widget.RecyclerView; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.SubMenu; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.ArrayList; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class NavigationMenuPresenter implements MenuPresenter { - - private static final String STATE_HIERARCHY = "android:menu:list"; - private static final String STATE_ADAPTER = "android:menu:adapter"; - private static final String STATE_HEADER = "android:menu:header"; - - private NavigationMenuView mMenuView; - LinearLayout mHeaderLayout; - - private Callback mCallback; - MenuBuilder mMenu; - private int mId; - - NavigationMenuAdapter mAdapter; - LayoutInflater mLayoutInflater; - - int mTextAppearance; - boolean mTextAppearanceSet; - ColorStateList mTextColor; - ColorStateList mIconTintList; - Drawable mItemBackground; - - /** - * Padding to be inserted at the top of the list to avoid the first menu item - * from being placed underneath the status bar. - */ - private int mPaddingTopDefault; - - /** - * Padding for separators between items - */ - int mPaddingSeparator; - - @Override - public void initForMenu(Context context, MenuBuilder menu) { - mLayoutInflater = LayoutInflater.from(context); - mMenu = menu; - Resources res = context.getResources(); - mPaddingSeparator = res.getDimensionPixelOffset( - R.dimen.design_navigation_separator_vertical_padding); - } - - @Override - public MenuView getMenuView(ViewGroup root) { - if (mMenuView == null) { - mMenuView = (NavigationMenuView) mLayoutInflater.inflate( - R.layout.design_navigation_menu, root, false); - if (mAdapter == null) { - mAdapter = new NavigationMenuAdapter(); - } - mHeaderLayout = (LinearLayout) mLayoutInflater - .inflate(R.layout.design_navigation_item_header, - mMenuView, false); - mMenuView.setAdapter(mAdapter); - } - return mMenuView; - } - - @Override - public void updateMenuView(boolean cleared) { - if (mAdapter != null) { - mAdapter.update(); - } - } - - @Override - public void setCallback(Callback cb) { - mCallback = cb; - } - - @Override - public boolean onSubMenuSelected(SubMenuBuilder subMenu) { - return false; - } - - @Override - public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { - if (mCallback != null) { - mCallback.onCloseMenu(menu, allMenusAreClosing); - } - } - - @Override - public boolean flagActionItems() { - return false; - } - - @Override - public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - @Override - public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - @Override - public int getId() { - return mId; - } - - public void setId(int id) { - mId = id; - } - - @Override - public Parcelable onSaveInstanceState() { - if (Build.VERSION.SDK_INT >= 11) { - // API 9-10 does not support ClassLoaderCreator, therefore things can crash if they're - // loaded via different loaders. Rather than crash we just won't save state on those - // platforms - final Bundle state = new Bundle(); - if (mMenuView != null) { - SparseArray<Parcelable> hierarchy = new SparseArray<>(); - mMenuView.saveHierarchyState(hierarchy); - state.putSparseParcelableArray(STATE_HIERARCHY, hierarchy); - } - if (mAdapter != null) { - state.putBundle(STATE_ADAPTER, mAdapter.createInstanceState()); - } - if (mHeaderLayout != null) { - SparseArray<Parcelable> header = new SparseArray<>(); - mHeaderLayout.saveHierarchyState(header); - state.putSparseParcelableArray(STATE_HEADER, header); - } - return state; - } - return null; - } - - @Override - public void onRestoreInstanceState(final Parcelable parcelable) { - if (parcelable instanceof Bundle) { - Bundle state = (Bundle) parcelable; - SparseArray<Parcelable> hierarchy = state.getSparseParcelableArray(STATE_HIERARCHY); - if (hierarchy != null) { - mMenuView.restoreHierarchyState(hierarchy); - } - Bundle adapterState = state.getBundle(STATE_ADAPTER); - if (adapterState != null) { - mAdapter.restoreInstanceState(adapterState); - } - SparseArray<Parcelable> header = state.getSparseParcelableArray(STATE_HEADER); - if (header != null) { - mHeaderLayout.restoreHierarchyState(header); - } - } - } - - public void setCheckedItem(MenuItemImpl item) { - mAdapter.setCheckedItem(item); - } - - public View inflateHeaderView(@LayoutRes int res) { - View view = mLayoutInflater.inflate(res, mHeaderLayout, false); - addHeaderView(view); - return view; - } - - public void addHeaderView(@NonNull View view) { - mHeaderLayout.addView(view); - // The padding on top should be cleared. - mMenuView.setPadding(0, 0, 0, mMenuView.getPaddingBottom()); - } - - public void removeHeaderView(@NonNull View view) { - mHeaderLayout.removeView(view); - if (mHeaderLayout.getChildCount() == 0) { - mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom()); - } - } - - public int getHeaderCount() { - return mHeaderLayout.getChildCount(); - } - - public View getHeaderView(int index) { - return mHeaderLayout.getChildAt(index); - } - - @Nullable - public ColorStateList getItemTintList() { - return mIconTintList; - } - - public void setItemIconTintList(@Nullable ColorStateList tint) { - mIconTintList = tint; - updateMenuView(false); - } - - @Nullable - public ColorStateList getItemTextColor() { - return mTextColor; - } - - public void setItemTextColor(@Nullable ColorStateList textColor) { - mTextColor = textColor; - updateMenuView(false); - } - - public void setItemTextAppearance(@StyleRes int resId) { - mTextAppearance = resId; - mTextAppearanceSet = true; - updateMenuView(false); - } - - @Nullable - public Drawable getItemBackground() { - return mItemBackground; - } - - public void setItemBackground(@Nullable Drawable itemBackground) { - mItemBackground = itemBackground; - updateMenuView(false); - } - - public void setUpdateSuspended(boolean updateSuspended) { - if (mAdapter != null) { - mAdapter.setUpdateSuspended(updateSuspended); - } - } - - public void dispatchApplyWindowInsets(WindowInsetsCompat insets) { - int top = insets.getSystemWindowInsetTop(); - if (mPaddingTopDefault != top) { - mPaddingTopDefault = top; - if (mHeaderLayout.getChildCount() == 0) { - mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom()); - } - } - ViewCompat.dispatchApplyWindowInsets(mHeaderLayout, insets); - } - - private abstract static class ViewHolder extends RecyclerView.ViewHolder { - - public ViewHolder(View itemView) { - super(itemView); - } - - } - - private static class NormalViewHolder extends ViewHolder { - - public NormalViewHolder(LayoutInflater inflater, ViewGroup parent, - View.OnClickListener listener) { - super(inflater.inflate(R.layout.design_navigation_item, parent, false)); - itemView.setOnClickListener(listener); - } - - } - - private static class SubheaderViewHolder extends ViewHolder { - - public SubheaderViewHolder(LayoutInflater inflater, ViewGroup parent) { - super(inflater.inflate(R.layout.design_navigation_item_subheader, parent, false)); - } - - } - - private static class SeparatorViewHolder extends ViewHolder { - - public SeparatorViewHolder(LayoutInflater inflater, ViewGroup parent) { - super(inflater.inflate(R.layout.design_navigation_item_separator, parent, false)); - } - - } - - private static class HeaderViewHolder extends ViewHolder { - - public HeaderViewHolder(View itemView) { - super(itemView); - } - - } - - /** - * Handles click events for the menu items. The items has to be {@link NavigationMenuItemView}. - */ - final View.OnClickListener mOnClickListener = new View.OnClickListener() { - - @Override - public void onClick(View v) { - NavigationMenuItemView itemView = (NavigationMenuItemView) v; - setUpdateSuspended(true); - MenuItemImpl item = itemView.getItemData(); - boolean result = mMenu.performItemAction(item, NavigationMenuPresenter.this, 0); - if (item != null && item.isCheckable() && result) { - mAdapter.setCheckedItem(item); - } - setUpdateSuspended(false); - updateMenuView(false); - } - - }; - - private class NavigationMenuAdapter extends RecyclerView.Adapter<ViewHolder> { - - private static final String STATE_CHECKED_ITEM = "android:menu:checked"; - - private static final String STATE_ACTION_VIEWS = "android:menu:action_views"; - private static final int VIEW_TYPE_NORMAL = 0; - private static final int VIEW_TYPE_SUBHEADER = 1; - private static final int VIEW_TYPE_SEPARATOR = 2; - private static final int VIEW_TYPE_HEADER = 3; - - private final ArrayList<NavigationMenuItem> mItems = new ArrayList<>(); - private MenuItemImpl mCheckedItem; - private boolean mUpdateSuspended; - - NavigationMenuAdapter() { - prepareMenuItems(); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getItemCount() { - return mItems.size(); - } - - @Override - public int getItemViewType(int position) { - NavigationMenuItem item = mItems.get(position); - if (item instanceof NavigationMenuSeparatorItem) { - return VIEW_TYPE_SEPARATOR; - } else if (item instanceof NavigationMenuHeaderItem) { - return VIEW_TYPE_HEADER; - } else if (item instanceof NavigationMenuTextItem) { - NavigationMenuTextItem textItem = (NavigationMenuTextItem) item; - if (textItem.getMenuItem().hasSubMenu()) { - return VIEW_TYPE_SUBHEADER; - } else { - return VIEW_TYPE_NORMAL; - } - } - throw new RuntimeException("Unknown item type."); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_TYPE_NORMAL: - return new NormalViewHolder(mLayoutInflater, parent, mOnClickListener); - case VIEW_TYPE_SUBHEADER: - return new SubheaderViewHolder(mLayoutInflater, parent); - case VIEW_TYPE_SEPARATOR: - return new SeparatorViewHolder(mLayoutInflater, parent); - case VIEW_TYPE_HEADER: - return new HeaderViewHolder(mHeaderLayout); - } - return null; - } - - @Override - public void onBindViewHolder(ViewHolder holder, int position) { - switch (getItemViewType(position)) { - case VIEW_TYPE_NORMAL: { - NavigationMenuItemView itemView = (NavigationMenuItemView) holder.itemView; - itemView.setIconTintList(mIconTintList); - if (mTextAppearanceSet) { - itemView.setTextAppearance(mTextAppearance); - } - if (mTextColor != null) { - itemView.setTextColor(mTextColor); - } - ViewCompat.setBackground(itemView, mItemBackground != null ? - mItemBackground.getConstantState().newDrawable() : null); - NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position); - itemView.setNeedsEmptyIcon(item.needsEmptyIcon); - itemView.initialize(item.getMenuItem(), 0); - break; - } - case VIEW_TYPE_SUBHEADER: { - TextView subHeader = (TextView) holder.itemView; - NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position); - subHeader.setText(item.getMenuItem().getTitle()); - break; - } - case VIEW_TYPE_SEPARATOR: { - NavigationMenuSeparatorItem item = - (NavigationMenuSeparatorItem) mItems.get(position); - holder.itemView.setPadding(0, item.getPaddingTop(), 0, - item.getPaddingBottom()); - break; - } - case VIEW_TYPE_HEADER: { - break; - } - } - - } - - @Override - public void onViewRecycled(ViewHolder holder) { - if (holder instanceof NormalViewHolder) { - ((NavigationMenuItemView) holder.itemView).recycle(); - } - } - - public void update() { - prepareMenuItems(); - notifyDataSetChanged(); - } - - /** - * Flattens the visible menu items of {@link #mMenu} into {@link #mItems}, - * while inserting separators between items when necessary. - */ - private void prepareMenuItems() { - if (mUpdateSuspended) { - return; - } - mUpdateSuspended = true; - mItems.clear(); - mItems.add(new NavigationMenuHeaderItem()); - - int currentGroupId = -1; - int currentGroupStart = 0; - boolean currentGroupHasIcon = false; - for (int i = 0, totalSize = mMenu.getVisibleItems().size(); i < totalSize; i++) { - MenuItemImpl item = mMenu.getVisibleItems().get(i); - if (item.isChecked()) { - setCheckedItem(item); - } - if (item.isCheckable()) { - item.setExclusiveCheckable(false); - } - if (item.hasSubMenu()) { - SubMenu subMenu = item.getSubMenu(); - if (subMenu.hasVisibleItems()) { - if (i != 0) { - mItems.add(new NavigationMenuSeparatorItem(mPaddingSeparator, 0)); - } - mItems.add(new NavigationMenuTextItem(item)); - boolean subMenuHasIcon = false; - int subMenuStart = mItems.size(); - for (int j = 0, size = subMenu.size(); j < size; j++) { - MenuItemImpl subMenuItem = (MenuItemImpl) subMenu.getItem(j); - if (subMenuItem.isVisible()) { - if (!subMenuHasIcon && subMenuItem.getIcon() != null) { - subMenuHasIcon = true; - } - if (subMenuItem.isCheckable()) { - subMenuItem.setExclusiveCheckable(false); - } - if (item.isChecked()) { - setCheckedItem(item); - } - mItems.add(new NavigationMenuTextItem(subMenuItem)); - } - } - if (subMenuHasIcon) { - appendTransparentIconIfMissing(subMenuStart, mItems.size()); - } - } - } else { - int groupId = item.getGroupId(); - if (groupId != currentGroupId) { // first item in group - currentGroupStart = mItems.size(); - currentGroupHasIcon = item.getIcon() != null; - if (i != 0) { - currentGroupStart++; - mItems.add(new NavigationMenuSeparatorItem( - mPaddingSeparator, mPaddingSeparator)); - } - } else if (!currentGroupHasIcon && item.getIcon() != null) { - currentGroupHasIcon = true; - appendTransparentIconIfMissing(currentGroupStart, mItems.size()); - } - NavigationMenuTextItem textItem = new NavigationMenuTextItem(item); - textItem.needsEmptyIcon = currentGroupHasIcon; - mItems.add(textItem); - currentGroupId = groupId; - } - } - mUpdateSuspended = false; - } - - private void appendTransparentIconIfMissing(int startIndex, int endIndex) { - for (int i = startIndex; i < endIndex; i++) { - NavigationMenuTextItem textItem = (NavigationMenuTextItem) mItems.get(i); - textItem.needsEmptyIcon = true; - } - } - - public void setCheckedItem(MenuItemImpl checkedItem) { - if (mCheckedItem == checkedItem || !checkedItem.isCheckable()) { - return; - } - if (mCheckedItem != null) { - mCheckedItem.setChecked(false); - } - mCheckedItem = checkedItem; - checkedItem.setChecked(true); - } - - public Bundle createInstanceState() { - Bundle state = new Bundle(); - if (mCheckedItem != null) { - state.putInt(STATE_CHECKED_ITEM, mCheckedItem.getItemId()); - } - // Store the states of the action views. - SparseArray<ParcelableSparseArray> actionViewStates = new SparseArray<>(); - for (int i = 0, size = mItems.size(); i < size; i++) { - NavigationMenuItem navigationMenuItem = mItems.get(i); - if (navigationMenuItem instanceof NavigationMenuTextItem) { - MenuItemImpl item = ((NavigationMenuTextItem) navigationMenuItem).getMenuItem(); - View actionView = item != null ? item.getActionView() : null; - if (actionView != null) { - ParcelableSparseArray container = new ParcelableSparseArray(); - actionView.saveHierarchyState(container); - actionViewStates.put(item.getItemId(), container); - } - } - } - state.putSparseParcelableArray(STATE_ACTION_VIEWS, actionViewStates); - return state; - } - - public void restoreInstanceState(Bundle state) { - int checkedItem = state.getInt(STATE_CHECKED_ITEM, 0); - if (checkedItem != 0) { - mUpdateSuspended = true; - for (int i = 0, size = mItems.size(); i < size; i++) { - NavigationMenuItem item = mItems.get(i); - if (item instanceof NavigationMenuTextItem) { - MenuItemImpl menuItem = ((NavigationMenuTextItem) item).getMenuItem(); - if (menuItem != null && menuItem.getItemId() == checkedItem) { - setCheckedItem(menuItem); - break; - } - } - } - mUpdateSuspended = false; - prepareMenuItems(); - } - // Restore the states of the action views. - SparseArray<ParcelableSparseArray> actionViewStates = state - .getSparseParcelableArray(STATE_ACTION_VIEWS); - if (actionViewStates != null) { - for (int i = 0, size = mItems.size(); i < size; i++) { - NavigationMenuItem navigationMenuItem = mItems.get(i); - if (!(navigationMenuItem instanceof NavigationMenuTextItem)) { - continue; - } - MenuItemImpl item = ((NavigationMenuTextItem) navigationMenuItem).getMenuItem(); - if (item == null) { - continue; - } - View actionView = item.getActionView(); - if (actionView == null) { - continue; - } - ParcelableSparseArray container = actionViewStates.get(item.getItemId()); - if (container == null) { - continue; - } - actionView.restoreHierarchyState(container); - } - } - } - - public void setUpdateSuspended(boolean updateSuspended) { - mUpdateSuspended = updateSuspended; - } - - } - - /** - * Unified data model for all sorts of navigation menu items. - */ - private interface NavigationMenuItem { - } - - /** - * Normal or subheader items. - */ - private static class NavigationMenuTextItem implements NavigationMenuItem { - - private final MenuItemImpl mMenuItem; - - boolean needsEmptyIcon; - - NavigationMenuTextItem(MenuItemImpl item) { - mMenuItem = item; - } - - public MenuItemImpl getMenuItem() { - return mMenuItem; - } - - } - - /** - * Separator items. - */ - private static class NavigationMenuSeparatorItem implements NavigationMenuItem { - - private final int mPaddingTop; - - private final int mPaddingBottom; - - public NavigationMenuSeparatorItem(int paddingTop, int paddingBottom) { - mPaddingTop = paddingTop; - mPaddingBottom = paddingBottom; - } - - public int getPaddingTop() { - return mPaddingTop; - } - - public int getPaddingBottom() { - return mPaddingBottom; - } - - } - - /** - * Header (not subheader) items. - */ - private static class NavigationMenuHeaderItem implements NavigationMenuItem { - NavigationMenuHeaderItem() { - } - // The actual content is hold by NavigationMenuPresenter#mHeaderLayout. - } - -} diff --git a/android/support/design/internal/NavigationMenuView.java b/android/support/design/internal/NavigationMenuView.java deleted file mode 100644 index 711f71ee..00000000 --- a/android/support/design/internal/NavigationMenuView.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.support.annotation.RestrictTo; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class NavigationMenuView extends RecyclerView implements MenuView { - - public NavigationMenuView(Context context) { - this(context, null); - } - - public NavigationMenuView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public NavigationMenuView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); - } - - @Override - public void initialize(MenuBuilder menu) { - - } - - @Override - public int getWindowAnimations() { - return 0; - } - -} diff --git a/android/support/design/internal/NavigationSubMenu.java b/android/support/design/internal/NavigationSubMenu.java deleted file mode 100644 index 1ff1e4f4..00000000 --- a/android/support/design/internal/NavigationSubMenu.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.support.annotation.RestrictTo; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.view.menu.SubMenuBuilder; - -/** - * This is a {@link SubMenuBuilder} that it notifies the parent {@link NavigationMenu} of its menu - * updates. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class NavigationSubMenu extends SubMenuBuilder { - - public NavigationSubMenu(Context context, NavigationMenu menu, MenuItemImpl item) { - super(context, menu, item); - } - - @Override - public void onItemsChanged(boolean structureChanged) { - super.onItemsChanged(structureChanged); - ((MenuBuilder) getParentMenu()).onItemsChanged(structureChanged); - } - -} diff --git a/android/support/design/internal/ParcelableSparseArray.java b/android/support/design/internal/ParcelableSparseArray.java deleted file mode 100644 index b29000e6..00000000 --- a/android/support/design/internal/ParcelableSparseArray.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.RestrictTo; -import android.util.SparseArray; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable { - - public ParcelableSparseArray() { - super(); - } - - public ParcelableSparseArray(Parcel source, ClassLoader loader) { - super(); - int size = source.readInt(); - int[] keys = new int[size]; - source.readIntArray(keys); - Parcelable[] values = source.readParcelableArray(loader); - for (int i = 0; i < size; ++i) { - put(keys[i], values[i]); - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - int size = size(); - int[] keys = new int[size]; - Parcelable[] values = new Parcelable[size]; - for (int i = 0; i < size; ++i) { - keys[i] = keyAt(i); - values[i] = valueAt(i); - } - parcel.writeInt(size); - parcel.writeIntArray(keys); - parcel.writeParcelableArray(values, flags); - } - - public static final Creator<ParcelableSparseArray> CREATOR = - new ClassLoaderCreator<ParcelableSparseArray>() { - @Override - public ParcelableSparseArray createFromParcel(Parcel source, ClassLoader loader) { - return new ParcelableSparseArray(source, loader); - } - - @Override - public ParcelableSparseArray createFromParcel(Parcel source) { - return new ParcelableSparseArray(source, null); - } - - @Override - public ParcelableSparseArray[] newArray(int size) { - return new ParcelableSparseArray[size]; - } - }; -} diff --git a/android/support/design/internal/ScrimInsetsFrameLayout.java b/android/support/design/internal/ScrimInsetsFrameLayout.java deleted file mode 100644 index 38f5b29b..00000000 --- a/android/support/design/internal/ScrimInsetsFrameLayout.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.design.R; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.WindowInsetsCompat; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class ScrimInsetsFrameLayout extends FrameLayout { - - Drawable mInsetForeground; - - Rect mInsets; - - private Rect mTempRect = new Rect(); - - public ScrimInsetsFrameLayout(Context context) { - this(context, null); - } - - public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.ScrimInsetsFrameLayout, defStyleAttr, - R.style.Widget_Design_ScrimInsetsFrameLayout); - mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsFrameLayout_insetForeground); - a.recycle(); - setWillNotDraw(true); // No need to draw until the insets are adjusted - - ViewCompat.setOnApplyWindowInsetsListener(this, - new android.support.v4.view.OnApplyWindowInsetsListener() { - @Override - public WindowInsetsCompat onApplyWindowInsets(View v, - WindowInsetsCompat insets) { - if (null == mInsets) { - mInsets = new Rect(); - } - mInsets.set(insets.getSystemWindowInsetLeft(), - insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight(), - insets.getSystemWindowInsetBottom()); - onInsetsChanged(insets); - setWillNotDraw(!insets.hasSystemWindowInsets() || mInsetForeground == null); - ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this); - return insets.consumeSystemWindowInsets(); - } - }); - } - - @Override - public void draw(@NonNull Canvas canvas) { - super.draw(canvas); - - int width = getWidth(); - int height = getHeight(); - if (mInsets != null && mInsetForeground != null) { - int sc = canvas.save(); - canvas.translate(getScrollX(), getScrollY()); - - // Top - mTempRect.set(0, 0, width, mInsets.top); - mInsetForeground.setBounds(mTempRect); - mInsetForeground.draw(canvas); - - // Bottom - mTempRect.set(0, height - mInsets.bottom, width, height); - mInsetForeground.setBounds(mTempRect); - mInsetForeground.draw(canvas); - - // Left - mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom); - mInsetForeground.setBounds(mTempRect); - mInsetForeground.draw(canvas); - - // Right - mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom); - mInsetForeground.setBounds(mTempRect); - mInsetForeground.draw(canvas); - - canvas.restoreToCount(sc); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mInsetForeground != null) { - mInsetForeground.setCallback(this); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mInsetForeground != null) { - mInsetForeground.setCallback(null); - } - } - - protected void onInsetsChanged(WindowInsetsCompat insets) { - } - -} diff --git a/android/support/design/internal/SnackbarContentLayout.java b/android/support/design/internal/SnackbarContentLayout.java deleted file mode 100644 index 2abf0127..00000000 --- a/android/support/design/internal/SnackbarContentLayout.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2016 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.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.RestrictTo; -import android.support.design.R; -import android.support.design.widget.BaseTransientBottomBar; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TextView; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class SnackbarContentLayout extends LinearLayout implements - BaseTransientBottomBar.ContentViewCallback { - private TextView mMessageView; - private Button mActionView; - - private int mMaxWidth; - private int mMaxInlineActionWidth; - - public SnackbarContentLayout(Context context) { - this(context, null); - } - - public SnackbarContentLayout(Context context, AttributeSet attrs) { - super(context, attrs); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout); - mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1); - mMaxInlineActionWidth = a.getDimensionPixelSize( - R.styleable.SnackbarLayout_maxActionInlineWidth, -1); - a.recycle(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mMessageView = findViewById(R.id.snackbar_text); - mActionView = findViewById(R.id.snackbar_action); - } - - public TextView getMessageView() { - return mMessageView; - } - - public Button getActionView() { - return mActionView; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (mMaxWidth > 0 && getMeasuredWidth() > mMaxWidth) { - widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - final int multiLineVPadding = getResources().getDimensionPixelSize( - R.dimen.design_snackbar_padding_vertical_2lines); - final int singleLineVPadding = getResources().getDimensionPixelSize( - R.dimen.design_snackbar_padding_vertical); - final boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1; - - boolean remeasure = false; - if (isMultiLine && mMaxInlineActionWidth > 0 - && mActionView.getMeasuredWidth() > mMaxInlineActionWidth) { - if (updateViewsWithinLayout(VERTICAL, multiLineVPadding, - multiLineVPadding - singleLineVPadding)) { - remeasure = true; - } - } else { - final int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding; - if (updateViewsWithinLayout(HORIZONTAL, messagePadding, messagePadding)) { - remeasure = true; - } - } - - if (remeasure) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - private boolean updateViewsWithinLayout(final int orientation, - final int messagePadTop, final int messagePadBottom) { - boolean changed = false; - if (orientation != getOrientation()) { - setOrientation(orientation); - changed = true; - } - if (mMessageView.getPaddingTop() != messagePadTop - || mMessageView.getPaddingBottom() != messagePadBottom) { - updateTopBottomPadding(mMessageView, messagePadTop, messagePadBottom); - changed = true; - } - return changed; - } - - private static void updateTopBottomPadding(View view, int topPadding, int bottomPadding) { - if (ViewCompat.isPaddingRelative(view)) { - ViewCompat.setPaddingRelative(view, - ViewCompat.getPaddingStart(view), topPadding, - ViewCompat.getPaddingEnd(view), bottomPadding); - } else { - view.setPadding(view.getPaddingLeft(), topPadding, - view.getPaddingRight(), bottomPadding); - } - } - - @Override - public void animateContentIn(int delay, int duration) { - mMessageView.setAlpha(0f); - mMessageView.animate().alpha(1f).setDuration(duration) - .setStartDelay(delay).start(); - - if (mActionView.getVisibility() == VISIBLE) { - mActionView.setAlpha(0f); - mActionView.animate().alpha(1f).setDuration(duration) - .setStartDelay(delay).start(); - } - } - - @Override - public void animateContentOut(int delay, int duration) { - mMessageView.setAlpha(1f); - mMessageView.animate().alpha(0f).setDuration(duration) - .setStartDelay(delay).start(); - - if (mActionView.getVisibility() == VISIBLE) { - mActionView.setAlpha(1f); - mActionView.animate().alpha(0f).setDuration(duration) - .setStartDelay(delay).start(); - } - } -} diff --git a/android/support/design/internal/TextScale.java b/android/support/design/internal/TextScale.java deleted file mode 100644 index 06c94729..00000000 --- a/android/support/design/internal/TextScale.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 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.internal; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.support.annotation.RequiresApi; -import android.support.annotation.RestrictTo; -import android.support.transition.Transition; -import android.support.transition.TransitionValues; -import android.view.ViewGroup; -import android.widget.TextView; - -import java.util.Map; - -/** - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -@RequiresApi(14) -public class TextScale extends Transition { - private static final String PROPNAME_SCALE = "android:textscale:scale"; - - @Override - public void captureStartValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - private void captureValues(TransitionValues transitionValues) { - if (transitionValues.view instanceof TextView) { - TextView textview = (TextView) transitionValues.view; - transitionValues.values.put(PROPNAME_SCALE, textview.getScaleX()); - } - } - - @Override - public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, - TransitionValues endValues) { - if (startValues == null || endValues == null || !(startValues.view instanceof TextView) - || !(endValues.view instanceof TextView)) { - return null; - } - final TextView view = (TextView) endValues.view; - Map<String, Object> startVals = startValues.values; - Map<String, Object> endVals = endValues.values; - final float startSize = startVals.get(PROPNAME_SCALE) != null ? (float) startVals.get( - PROPNAME_SCALE) : 1f; - final float endSize = endVals.get(PROPNAME_SCALE) != null ? (float) endVals.get( - PROPNAME_SCALE) : 1f; - if (startSize == endSize) { - return null; - } - - ValueAnimator animator = ValueAnimator.ofFloat(startSize, endSize); - - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - float animatedValue = (float) valueAnimator.getAnimatedValue(); - view.setScaleX(animatedValue); - view.setScaleY(animatedValue); - } - }); - return animator; - } -} diff --git a/android/support/design/internal/package-info.java b/android/support/design/internal/package-info.java deleted file mode 100644 index 6b6f7bbd..00000000 --- a/android/support/design/internal/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -package android.support.design.internal; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.support.annotation.RestrictTo; diff --git a/android/support/design/widget/AnimationUtils.java b/android/support/design/widget/AnimationUtils.java deleted file mode 100644 index 3613afd8..00000000 --- a/android/support/design/widget/AnimationUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.support.v4.view.animation.FastOutLinearInInterpolator; -import android.support.v4.view.animation.FastOutSlowInInterpolator; -import android.support.v4.view.animation.LinearOutSlowInInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; - -class AnimationUtils { - - static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); - static final Interpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator(); - static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR = new FastOutLinearInInterpolator(); - static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR = new LinearOutSlowInInterpolator(); - static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); - - /** - * Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}. - */ - static float lerp(float startValue, float endValue, float fraction) { - return startValue + (fraction * (endValue - startValue)); - } - - static int lerp(int startValue, int endValue, float fraction) { - return startValue + Math.round(fraction * (endValue - startValue)); - } - -} diff --git a/android/support/design/widget/AppBarLayout.java b/android/support/design/widget/AppBarLayout.java deleted file mode 100644 index 8304cd6a..00000000 --- a/android/support/design/widget/AppBarLayout.java +++ /dev/null @@ -1,1474 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.support.annotation.RestrictTo; -import android.support.annotation.VisibleForTesting; -import android.support.design.R; -import android.support.v4.math.MathUtils; -import android.support.v4.util.ObjectsCompat; -import android.support.v4.view.AbsSavedState; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.WindowInsetsCompat; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Interpolator; -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.List; - -/** - * AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of - * material designs 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: - * {@code app:layout_scrollFlags}. - * - * <p> - * This view depends heavily on being used as a direct child within a {@link CoordinatorLayout}. - * If you use AppBarLayout within a different {@link ViewGroup}, most of it's functionality will - * not work. - * <p> - * AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. - * The binding is done through the {@link ScrollingViewBehavior} behavior class, meaning that you - * should set your scrolling view's behavior to be an instance of {@link ScrollingViewBehavior}. - * A string resource containing the full class name is available. - * - * <pre> - * <android.support.design.widget.CoordinatorLayout - * xmlns:android="http://schemas.android.com/apk/res/android" - * xmlns:app="http://schemas.android.com/apk/res-auto" - * android:layout_width="match_parent" - * android:layout_height="match_parent"> - * - * <android.support.v4.widget.NestedScrollView - * android:layout_width="match_parent" - * android:layout_height="match_parent" - * app:layout_behavior="@string/appbar_scrolling_view_behavior"> - * - * <!-- Your scrolling content --> - * - * </android.support.v4.widget.NestedScrollView> - * - * <android.support.design.widget.AppBarLayout - * android:layout_height="wrap_content" - * android:layout_width="match_parent"> - * - * <android.support.v7.widget.Toolbar - * ... - * app:layout_scrollFlags="scroll|enterAlways"/> - * - * <android.support.design.widget.TabLayout - * ... - * app:layout_scrollFlags="scroll|enterAlways"/> - * - * </android.support.design.widget.AppBarLayout> - * - * </android.support.design.widget.CoordinatorLayout> - * </pre> - * - * @see <a href="http://www.google.com/design/spec/layout/structure.html#structure-app-bar"> - * http://www.google.com/design/spec/layout/structure.html#structure-app-bar</a> - */ -@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class) -public class AppBarLayout extends LinearLayout { - - static final int PENDING_ACTION_NONE = 0x0; - static final int PENDING_ACTION_EXPANDED = 0x1; - static final int PENDING_ACTION_COLLAPSED = 0x2; - static final int PENDING_ACTION_ANIMATE_ENABLED = 0x4; - static final int PENDING_ACTION_FORCE = 0x8; - - /** - * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical - * offset changes. - */ - 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). - * - * @param appBarLayout the {@link AppBarLayout} which offset has changed - * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px - */ - void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset); - } - - private static final int INVALID_SCROLL_RANGE = -1; - - private int mTotalScrollRange = INVALID_SCROLL_RANGE; - private int mDownPreScrollRange = INVALID_SCROLL_RANGE; - private int mDownScrollRange = INVALID_SCROLL_RANGE; - - private boolean mHaveChildWithInterpolator; - - private int mPendingAction = PENDING_ACTION_NONE; - - private WindowInsetsCompat mLastInsets; - - private List<OnOffsetChangedListener> mListeners; - - private boolean mCollapsible; - private boolean mCollapsed; - - private int[] mTmpStatesArray; - - public AppBarLayout(Context context) { - this(context, null); - } - - public AppBarLayout(Context context, AttributeSet attrs) { - super(context, attrs); - setOrientation(VERTICAL); - - ThemeUtils.checkAppCompatTheme(context); - - if (Build.VERSION.SDK_INT >= 21) { - // Use the bounds view outline provider so that we cast a shadow, even without a - // background - ViewUtilsLollipop.setBoundsViewOutlineProvider(this); - - // If we're running on API 21+, we should reset any state list animator from our - // default style - ViewUtilsLollipop.setStateListAnimatorFromAttrs(this, attrs, 0, - R.style.Widget_Design_AppBarLayout); - } - - final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout, - 0, R.style.Widget_Design_AppBarLayout); - ViewCompat.setBackground(this, a.getDrawable(R.styleable.AppBarLayout_android_background)); - if (a.hasValue(R.styleable.AppBarLayout_expanded)) { - setExpanded(a.getBoolean(R.styleable.AppBarLayout_expanded, false), false, false); - } - if (Build.VERSION.SDK_INT >= 21 && a.hasValue(R.styleable.AppBarLayout_elevation)) { - ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator( - this, a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0)); - } - if (Build.VERSION.SDK_INT >= 26) { - // In O+, we have these values set in the style. Since there is no defStyleAttr for - // AppBarLayout at the AppCompat level, check for these attributes here. - if (a.hasValue(R.styleable.AppBarLayout_android_keyboardNavigationCluster)) { - this.setKeyboardNavigationCluster(a.getBoolean( - R.styleable.AppBarLayout_android_keyboardNavigationCluster, false)); - } - if (a.hasValue(R.styleable.AppBarLayout_android_touchscreenBlocksFocus)) { - this.setTouchscreenBlocksFocus(a.getBoolean( - R.styleable.AppBarLayout_android_touchscreenBlocksFocus, false)); - } - } - a.recycle(); - - ViewCompat.setOnApplyWindowInsetsListener(this, - new android.support.v4.view.OnApplyWindowInsetsListener() { - @Override - public WindowInsetsCompat onApplyWindowInsets(View v, - WindowInsetsCompat insets) { - return onWindowInsetChanged(insets); - } - }); - } - - /** - * 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) { - if (mListeners == null) { - mListeners = new ArrayList<>(); - } - if (listener != null && !mListeners.contains(listener)) { - mListeners.add(listener); - } - } - - /** - * Remove the previously added {@link OnOffsetChangedListener}. - * - * @param listener the listener to remove. - */ - public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) { - if (mListeners != null && listener != null) { - mListeners.remove(listener); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - invalidateScrollRanges(); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - invalidateScrollRanges(); - - mHaveChildWithInterpolator = false; - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - final LayoutParams childLp = (LayoutParams) child.getLayoutParams(); - final Interpolator interpolator = childLp.getScrollInterpolator(); - - if (interpolator != null) { - mHaveChildWithInterpolator = true; - break; - } - } - - updateCollapsible(); - } - - private void updateCollapsible() { - boolean haveCollapsibleChild = false; - for (int i = 0, z = getChildCount(); i < z; i++) { - if (((LayoutParams) getChildAt(i).getLayoutParams()).isCollapsible()) { - haveCollapsibleChild = true; - break; - } - } - setCollapsibleState(haveCollapsibleChild); - } - - private void invalidateScrollRanges() { - // Invalidate the scroll ranges - mTotalScrollRange = INVALID_SCROLL_RANGE; - mDownPreScrollRange = INVALID_SCROLL_RANGE; - mDownScrollRange = INVALID_SCROLL_RANGE; - } - - @Override - public void setOrientation(int orientation) { - if (orientation != VERTICAL) { - throw new IllegalArgumentException("AppBarLayout is always vertical and does" - + " not support horizontal orientation"); - } - super.setOrientation(orientation); - } - - /** - * Sets whether this {@link AppBarLayout} is expanded or not, animating if it has already - * been laid out. - * - * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a - * direct child of a {@link CoordinatorLayout}.</p> - * - * @param expanded true if the layout should be fully expanded, false if it should - * be fully collapsed - * - * @attr ref android.support.design.R.styleable#AppBarLayout_expanded - */ - public void setExpanded(boolean expanded) { - setExpanded(expanded, ViewCompat.isLaidOut(this)); - } - - /** - * Sets whether this {@link AppBarLayout} is expanded or not. - * - * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a - * direct child of a {@link CoordinatorLayout}.</p> - * - * @param expanded true if the layout should be fully expanded, false if it should - * be fully collapsed - * @param animate Whether to animate to the new state - * - * @attr ref android.support.design.R.styleable#AppBarLayout_expanded - */ - public void setExpanded(boolean expanded, boolean animate) { - setExpanded(expanded, animate, true); - } - - private void setExpanded(boolean expanded, boolean animate, boolean force) { - mPendingAction = (expanded ? PENDING_ACTION_EXPANDED : PENDING_ACTION_COLLAPSED) - | (animate ? PENDING_ACTION_ANIMATE_ENABLED : 0) - | (force ? PENDING_ACTION_FORCE : 0); - requestLayout(); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams; - } - - @Override - protected LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - @Override - protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - if (Build.VERSION.SDK_INT >= 19 && p instanceof LinearLayout.LayoutParams) { - return new LayoutParams((LinearLayout.LayoutParams) p); - } else if (p instanceof MarginLayoutParams) { - return new LayoutParams((MarginLayoutParams) p); - } - return new LayoutParams(p); - } - - boolean hasChildWithInterpolator() { - return mHaveChildWithInterpolator; - } - - /** - * Returns the scroll range of all children. - * - * @return the scroll range in px - */ - public final int getTotalScrollRange() { - if (mTotalScrollRange != INVALID_SCROLL_RANGE) { - return mTotalScrollRange; - } - - int range = 0; - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - final int childHeight = child.getMeasuredHeight(); - final int flags = lp.mScrollFlags; - - if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { - // We're set to scroll so add the child's height - range += childHeight + lp.topMargin + lp.bottomMargin; - - if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { - // For a collapsing scroll, we to take the collapsed height into account. - // We also break straight away since later views can't scroll beneath - // us - range -= ViewCompat.getMinimumHeight(child); - break; - } - } else { - // As soon as a view doesn't have the scroll flag, we end the range calculation. - // This is because views below can not scroll under a fixed view. - break; - } - } - return mTotalScrollRange = Math.max(0, range - getTopInset()); - } - - boolean hasScrollableChildren() { - return getTotalScrollRange() != 0; - } - - /** - * Return the scroll range when scrolling up from a nested pre-scroll. - */ - int getUpNestedPreScrollRange() { - return getTotalScrollRange(); - } - - /** - * Return the scroll range when scrolling down from a nested pre-scroll. - */ - int getDownNestedPreScrollRange() { - if (mDownPreScrollRange != INVALID_SCROLL_RANGE) { - // If we already have a valid value, return it - return mDownPreScrollRange; - } - - int range = 0; - for (int i = getChildCount() - 1; i >= 0; i--) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - final int childHeight = child.getMeasuredHeight(); - final int flags = lp.mScrollFlags; - - if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) { - // First take the margin into account - range += lp.topMargin + lp.bottomMargin; - // The view has the quick return flag combination... - if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) { - // If they're set to enter collapsed, use the minimum height - range += ViewCompat.getMinimumHeight(child); - } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { - // Only enter by the amount of the collapsed height - range += childHeight - ViewCompat.getMinimumHeight(child); - } else { - // Else use the full height (minus the top inset) - range += childHeight - getTopInset(); - } - } else if (range > 0) { - // If we've hit an non-quick return scrollable view, and we've already hit a - // quick return view, return now - break; - } - } - return mDownPreScrollRange = Math.max(0, range); - } - - /** - * Return the scroll range when scrolling down from a nested scroll. - */ - int getDownNestedScrollRange() { - if (mDownScrollRange != INVALID_SCROLL_RANGE) { - // If we already have a valid value, return it - return mDownScrollRange; - } - - int range = 0; - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - int childHeight = child.getMeasuredHeight(); - childHeight += lp.topMargin + lp.bottomMargin; - - final int flags = lp.mScrollFlags; - - if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { - // We're set to scroll so add the child's height - range += childHeight; - - if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { - // For a collapsing exit scroll, we to take the collapsed height into account. - // We also break the range straight away since later views can't scroll - // beneath us - range -= ViewCompat.getMinimumHeight(child) + getTopInset(); - break; - } - } else { - // As soon as a view doesn't have the scroll flag, we end the range calculation. - // This is because views below can not scroll under a fixed view. - break; - } - } - return mDownScrollRange = Math.max(0, range); - } - - void dispatchOffsetUpdates(int offset) { - // Iterate backwards through the list so that most recently added listeners - // get the first chance to decide - if (mListeners != null) { - for (int i = 0, z = mListeners.size(); i < z; i++) { - final OnOffsetChangedListener listener = mListeners.get(i); - if (listener != null) { - listener.onOffsetChanged(this, offset); - } - } - } - } - - final int getMinimumHeightForVisibleOverlappingContent() { - final int topInset = getTopInset(); - final int minHeight = ViewCompat.getMinimumHeight(this); - if (minHeight != 0) { - // If this layout has a min height, use it (doubled) - return (minHeight * 2) + topInset; - } - - // Otherwise, we'll use twice the min height of our last child - final int childCount = getChildCount(); - final int lastChildMinHeight = childCount >= 1 - ? ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) : 0; - if (lastChildMinHeight != 0) { - return (lastChildMinHeight * 2) + topInset; - } - - // If we reach here then we don't have a min height explicitly set. Instead we'll take a - // guess at 1/3 of our height being visible - return getHeight() / 3; - } - - @Override - protected int[] onCreateDrawableState(int extraSpace) { - if (mTmpStatesArray == null) { - // Note that we can't allocate this at the class level (in declaration) since - // some paths in super View constructor are going to call this method before - // that - mTmpStatesArray = new int[2]; - } - final int[] extraStates = mTmpStatesArray; - final int[] states = super.onCreateDrawableState(extraSpace + extraStates.length); - - extraStates[0] = mCollapsible ? R.attr.state_collapsible : -R.attr.state_collapsible; - extraStates[1] = mCollapsible && mCollapsed - ? R.attr.state_collapsed : -R.attr.state_collapsed; - - return mergeDrawableStates(states, extraStates); - } - - /** - * Sets whether the AppBarLayout has collapsible children or not. - * - * @return true if the collapsible state changed - */ - private boolean setCollapsibleState(boolean collapsible) { - if (mCollapsible != collapsible) { - mCollapsible = collapsible; - refreshDrawableState(); - return true; - } - return false; - } - - /** - * Sets whether the AppBarLayout is in a collapsed state or not. - * - * @return true if the collapsed state changed - */ - boolean setCollapsedState(boolean collapsed) { - if (mCollapsed != collapsed) { - mCollapsed = collapsed; - refreshDrawableState(); - return true; - } - return false; - } - - /** - * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now - * controlled via a {@link android.animation.StateListAnimator}. If a target - * elevation is set, either by this method or the {@code app:elevation} attribute, - * a new state list animator is created which uses the given {@code elevation} value. - * - * @attr ref android.support.design.R.styleable#AppBarLayout_elevation - */ - @Deprecated - public void setTargetElevation(float elevation) { - if (Build.VERSION.SDK_INT >= 21) { - ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(this, elevation); - } - } - - /** - * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now - * controlled via a {@link android.animation.StateListAnimator}. This method now - * always returns 0. - */ - @Deprecated - public float getTargetElevation() { - return 0; - } - - int getPendingAction() { - return mPendingAction; - } - - void resetPendingAction() { - mPendingAction = PENDING_ACTION_NONE; - } - - @VisibleForTesting - final int getTopInset() { - return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; - } - - WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) { - WindowInsetsCompat newInsets = null; - - if (ViewCompat.getFitsSystemWindows(this)) { - // If we're set to fit system windows, keep the insets - newInsets = insets; - } - - // If our insets have changed, keep them and invalidate the scroll ranges... - if (!ObjectsCompat.equals(mLastInsets, newInsets)) { - mLastInsets = newInsets; - invalidateScrollRanges(); - } - - return insets; - } - - public static class LayoutParams extends LinearLayout.LayoutParams { - - /** @hide */ - @RestrictTo(LIBRARY_GROUP) - @IntDef(flag=true, value={ - SCROLL_FLAG_SCROLL, - SCROLL_FLAG_EXIT_UNTIL_COLLAPSED, - SCROLL_FLAG_ENTER_ALWAYS, - SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED, - SCROLL_FLAG_SNAP - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ScrollFlags {} - - /** - * The view will be scroll in direct relation to scroll events. This flag needs to be - * set for any of the other flags to take effect. If any sibling views - * before this one do not have this flag, then this value has no effect. - */ - public static final int SCROLL_FLAG_SCROLL = 0x1; - - /** - * When exiting (scrolling off screen) the view will be scrolled until it is - * 'collapsed'. The collapsed height is defined by the view's minimum height. - * - * @see ViewCompat#getMinimumHeight(View) - * @see View#setMinimumHeight(int) - */ - public static final int SCROLL_FLAG_EXIT_UNTIL_COLLAPSED = 0x2; - - /** - * When entering (scrolling on screen) the view will scroll on any downwards - * scroll event, regardless of whether the scrolling view is also scrolling. This - * is commonly referred to as the 'quick return' pattern. - */ - public static final int SCROLL_FLAG_ENTER_ALWAYS = 0x4; - - /** - * An additional flag for 'enterAlways' which modifies the returning view to - * only initially scroll back to it's collapsed height. Once the scrolling view has - * reached the end of it's scroll range, the remainder of this view will be scrolled - * into view. The collapsed height is defined by the view's minimum height. - * - * @see ViewCompat#getMinimumHeight(View) - * @see View#setMinimumHeight(int) - */ - public static final int SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED = 0x8; - - /** - * Upon a scroll ending, if the view is only partially visible then it will be snapped - * and scrolled to it's closest edge. For example, if the view only has it's bottom 25% - * displayed, it will be scrolled off screen completely. Conversely, if it's bottom 75% - * is visible then it will be scrolled fully into view. - */ - public static final int SCROLL_FLAG_SNAP = 0x10; - - /** - * Internal flags which allows quick checking features - */ - static final int FLAG_QUICK_RETURN = SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS; - static final int FLAG_SNAP = SCROLL_FLAG_SCROLL | SCROLL_FLAG_SNAP; - static final int COLLAPSIBLE_FLAGS = SCROLL_FLAG_EXIT_UNTIL_COLLAPSED - | SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED; - - int mScrollFlags = SCROLL_FLAG_SCROLL; - Interpolator mScrollInterpolator; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_Layout); - mScrollFlags = a.getInt(R.styleable.AppBarLayout_Layout_layout_scrollFlags, 0); - if (a.hasValue(R.styleable.AppBarLayout_Layout_layout_scrollInterpolator)) { - int resId = a.getResourceId( - R.styleable.AppBarLayout_Layout_layout_scrollInterpolator, 0); - mScrollInterpolator = android.view.animation.AnimationUtils.loadInterpolator( - c, resId); - } - a.recycle(); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(int width, int height, float weight) { - super(width, height, weight); - } - - public LayoutParams(ViewGroup.LayoutParams p) { - super(p); - } - - public LayoutParams(MarginLayoutParams source) { - super(source); - } - - @RequiresApi(19) - public LayoutParams(LinearLayout.LayoutParams source) { - // The copy constructor called here only exists on API 19+. - super(source); - } - - @RequiresApi(19) - public LayoutParams(LayoutParams source) { - // The copy constructor called here only exists on API 19+. - super(source); - mScrollFlags = source.mScrollFlags; - mScrollInterpolator = source.mScrollInterpolator; - } - - /** - * Set the scrolling flags. - * - * @param flags bitwise int of {@link #SCROLL_FLAG_SCROLL}, - * {@link #SCROLL_FLAG_EXIT_UNTIL_COLLAPSED}, {@link #SCROLL_FLAG_ENTER_ALWAYS}, - * {@link #SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED} and {@link #SCROLL_FLAG_SNAP }. - * - * @see #getScrollFlags() - * - * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollFlags - */ - public void setScrollFlags(@ScrollFlags int flags) { - mScrollFlags = flags; - } - - /** - * Returns the scrolling flags. - * - * @see #setScrollFlags(int) - * - * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollFlags - */ - @ScrollFlags - public int getScrollFlags() { - return mScrollFlags; - } - - /** - * Set the interpolator to when scrolling the view associated with this - * {@link LayoutParams}. - * - * @param interpolator the interpolator to use, or null to use normal 1-to-1 scrolling. - * - * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollInterpolator - * @see #getScrollInterpolator() - */ - public void setScrollInterpolator(Interpolator interpolator) { - mScrollInterpolator = interpolator; - } - - /** - * Returns the {@link Interpolator} being used for scrolling the view associated with this - * {@link LayoutParams}. Null indicates 'normal' 1-to-1 scrolling. - * - * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollInterpolator - * @see #setScrollInterpolator(Interpolator) - */ - public Interpolator getScrollInterpolator() { - return mScrollInterpolator; - } - - /** - * Returns true if the scroll flags are compatible for 'collapsing' - */ - boolean isCollapsible() { - return (mScrollFlags & SCROLL_FLAG_SCROLL) == SCROLL_FLAG_SCROLL - && (mScrollFlags & COLLAPSIBLE_FLAGS) != 0; - } - } - - /** - * The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested - * scroll handling with offsetting. - */ - public static class Behavior extends HeaderBehavior<AppBarLayout> { - private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms - private static final int INVALID_POSITION = -1; - - /** - * Callback to allow control over any {@link AppBarLayout} dragging. - */ - public static abstract class DragCallback { - /** - * Allows control over whether the given {@link AppBarLayout} can be dragged or not. - * - * <p>Dragging is defined as a direct touch on the AppBarLayout with movement. This - * call does not affect any nested scrolling.</p> - * - * @return true if we are in a position to scroll the AppBarLayout via a drag, false - * if not. - */ - public abstract boolean canDrag(@NonNull AppBarLayout appBarLayout); - } - - private int mOffsetDelta; - private ValueAnimator mOffsetAnimator; - - private int mOffsetToChildIndexOnLayout = INVALID_POSITION; - private boolean mOffsetToChildIndexOnLayoutIsMinHeight; - private float mOffsetToChildIndexOnLayoutPerc; - - private WeakReference<View> mLastNestedScrollingChildRef; - private DragCallback mOnDragCallback; - - public Behavior() {} - - public Behavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, - View directTargetChild, View target, int nestedScrollAxes, int type) { - // Return true if we're nested scrolling vertically, and we have scrollable children - // and the scrolling view is big enough to scroll - final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 - && child.hasScrollableChildren() - && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight(); - - if (started && mOffsetAnimator != null) { - // Cancel any offset animation - mOffsetAnimator.cancel(); - } - - // A new nested scroll has started so clear out the previous ref - mLastNestedScrollingChildRef = null; - - return started; - } - - @Override - public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, - View target, int dx, int dy, int[] consumed, int type) { - if (dy != 0) { - int min, max; - if (dy < 0) { - // We're scrolling down - min = -child.getTotalScrollRange(); - max = min + child.getDownNestedPreScrollRange(); - } else { - // We're scrolling up - min = -child.getUpNestedPreScrollRange(); - max = 0; - } - if (min != max) { - consumed[1] = scroll(coordinatorLayout, child, dy, min, max); - } - } - } - - @Override - public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, - View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, - int type) { - if (dyUnconsumed < 0) { - // If the scrolling view is scrolling down but not consuming, it's probably be at - // the top of it's content - scroll(coordinatorLayout, child, dyUnconsumed, - -child.getDownNestedScrollRange(), 0); - } - } - - @Override - public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, - View target, int type) { - if (type == ViewCompat.TYPE_TOUCH) { - // If we haven't been flung then let's see if the current view has been set to snap - snapToChildIfNeeded(coordinatorLayout, abl); - } - - // Keep a reference to the previous nested scrolling child - mLastNestedScrollingChildRef = new WeakReference<>(target); - } - - /** - * Set a callback to control any {@link AppBarLayout} dragging. - * - * @param callback the callback to use, or {@code null} to use the default behavior. - */ - public void setDragCallback(@Nullable DragCallback callback) { - mOnDragCallback = callback; - } - - private void animateOffsetTo(final CoordinatorLayout coordinatorLayout, - final AppBarLayout child, final int offset, float velocity) { - final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset); - - final int duration; - velocity = Math.abs(velocity); - if (velocity > 0) { - duration = 3 * Math.round(1000 * (distance / velocity)); - } else { - final float distanceRatio = (float) distance / child.getHeight(); - duration = (int) ((distanceRatio + 1) * 150); - } - - animateOffsetWithDuration(coordinatorLayout, child, offset, duration); - } - - private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout, - final AppBarLayout child, final int offset, final int duration) { - final int currentOffset = getTopBottomOffsetForScrollingSibling(); - if (currentOffset == offset) { - if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) { - mOffsetAnimator.cancel(); - } - return; - } - - if (mOffsetAnimator == null) { - mOffsetAnimator = new ValueAnimator(); - mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); - mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setHeaderTopBottomOffset(coordinatorLayout, child, - (int) animation.getAnimatedValue()); - } - }); - } else { - mOffsetAnimator.cancel(); - } - - mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION)); - mOffsetAnimator.setIntValues(currentOffset, offset); - mOffsetAnimator.start(); - } - - private int getChildIndexOnOffset(AppBarLayout abl, final int offset) { - for (int i = 0, count = abl.getChildCount(); i < count; i++) { - View child = abl.getChildAt(i); - if (child.getTop() <= -offset && child.getBottom() >= -offset) { - return i; - } - } - return -1; - } - - private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, AppBarLayout abl) { - final int offset = getTopBottomOffsetForScrollingSibling(); - final int offsetChildIndex = getChildIndexOnOffset(abl, offset); - if (offsetChildIndex >= 0) { - final View offsetChild = abl.getChildAt(offsetChildIndex); - final LayoutParams lp = (LayoutParams) offsetChild.getLayoutParams(); - final int flags = lp.getScrollFlags(); - - if ((flags & LayoutParams.FLAG_SNAP) == LayoutParams.FLAG_SNAP) { - // We're set the snap, so animate the offset to the nearest edge - int snapTop = -offsetChild.getTop(); - int snapBottom = -offsetChild.getBottom(); - - if (offsetChildIndex == abl.getChildCount() - 1) { - // If this is the last child, we need to take the top inset into account - snapBottom += abl.getTopInset(); - } - - if (checkFlag(flags, LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED)) { - // If the view is set only exit until it is collapsed, we'll abide by that - snapBottom += ViewCompat.getMinimumHeight(offsetChild); - } else if (checkFlag(flags, LayoutParams.FLAG_QUICK_RETURN - | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS)) { - // If it's set to always enter collapsed, it actually has two states. We - // select the state and then snap within the state - final int seam = snapBottom + ViewCompat.getMinimumHeight(offsetChild); - if (offset < seam) { - snapTop = seam; - } else { - snapBottom = seam; - } - } - - final int newOffset = offset < (snapBottom + snapTop) / 2 - ? snapBottom - : snapTop; - animateOffsetTo(coordinatorLayout, abl, - MathUtils.clamp(newOffset, -abl.getTotalScrollRange(), 0), 0); - } - } - } - - private static boolean checkFlag(final int flags, final int check) { - return (flags & check) == check; - } - - @Override - public boolean onMeasureChild(CoordinatorLayout parent, AppBarLayout child, - int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, - int heightUsed) { - final CoordinatorLayout.LayoutParams lp = - (CoordinatorLayout.LayoutParams) child.getLayoutParams(); - if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) { - // If the view is set to wrap on it's height, CoordinatorLayout by default will - // cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't - // what we actually want, so we measure it ourselves with an unspecified spec to - // allow the child to be larger than it's parent - parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed); - return true; - } - - // Let the parent handle it as normal - return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, - parentHeightMeasureSpec, heightUsed); - } - - @Override - public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, - int layoutDirection) { - boolean handled = super.onLayoutChild(parent, abl, layoutDirection); - - // The priority for for actions here is (first which is true wins): - // 1. forced pending actions - // 2. offsets for restorations - // 3. non-forced pending actions - final int pendingAction = abl.getPendingAction(); - if (mOffsetToChildIndexOnLayout >= 0 && (pendingAction & PENDING_ACTION_FORCE) == 0) { - View child = abl.getChildAt(mOffsetToChildIndexOnLayout); - int offset = -child.getBottom(); - if (mOffsetToChildIndexOnLayoutIsMinHeight) { - offset += ViewCompat.getMinimumHeight(child) + abl.getTopInset(); - } else { - offset += Math.round(child.getHeight() * mOffsetToChildIndexOnLayoutPerc); - } - setHeaderTopBottomOffset(parent, abl, offset); - } else if (pendingAction != PENDING_ACTION_NONE) { - final boolean animate = (pendingAction & PENDING_ACTION_ANIMATE_ENABLED) != 0; - if ((pendingAction & PENDING_ACTION_COLLAPSED) != 0) { - final int offset = -abl.getUpNestedPreScrollRange(); - if (animate) { - animateOffsetTo(parent, abl, offset, 0); - } else { - setHeaderTopBottomOffset(parent, abl, offset); - } - } else if ((pendingAction & PENDING_ACTION_EXPANDED) != 0) { - if (animate) { - animateOffsetTo(parent, abl, 0, 0); - } else { - setHeaderTopBottomOffset(parent, abl, 0); - } - } - } - - // Finally reset any pending states - abl.resetPendingAction(); - mOffsetToChildIndexOnLayout = INVALID_POSITION; - - // We may have changed size, so let's constrain the top and bottom offset correctly, - // just in case we're out of the bounds - setTopAndBottomOffset( - MathUtils.clamp(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0)); - - // Update the AppBarLayout's drawable state for any elevation changes. - // This is needed so that the elevation is set in the first layout, so that - // we don't get a visual elevation jump pre-N (due to the draw dispatch skip) - updateAppBarLayoutDrawableState(parent, abl, getTopAndBottomOffset(), 0, true); - - // Make sure we dispatch the offset update - abl.dispatchOffsetUpdates(getTopAndBottomOffset()); - - return handled; - } - - @Override - boolean canDragView(AppBarLayout view) { - if (mOnDragCallback != null) { - // If there is a drag callback set, it's in control - return mOnDragCallback.canDrag(view); - } - - // Else we'll use the default behaviour of seeing if it can scroll down - if (mLastNestedScrollingChildRef != null) { - // If we have a reference to a scrolling view, check it - final View scrollingView = mLastNestedScrollingChildRef.get(); - return scrollingView != null && scrollingView.isShown() - && !scrollingView.canScrollVertically(-1); - } else { - // Otherwise we assume that the scrolling view hasn't been scrolled and can drag. - return true; - } - } - - @Override - void onFlingFinished(CoordinatorLayout parent, AppBarLayout layout) { - // At the end of a manual fling, check to see if we need to snap to the edge-child - snapToChildIfNeeded(parent, layout); - } - - @Override - int getMaxDragOffset(AppBarLayout view) { - return -view.getDownNestedScrollRange(); - } - - @Override - int getScrollRangeForDragFling(AppBarLayout view) { - return view.getTotalScrollRange(); - } - - @Override - int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout, - AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) { - final int curOffset = getTopBottomOffsetForScrollingSibling(); - int consumed = 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.clamp(newOffset, minOffset, maxOffset); - if (curOffset != newOffset) { - final int interpolatedOffset = appBarLayout.hasChildWithInterpolator() - ? interpolateOffset(appBarLayout, newOffset) - : newOffset; - - final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset); - - // Update how much dy we have consumed - consumed = curOffset - newOffset; - // Update the stored sibling offset - mOffsetDelta = newOffset - interpolatedOffset; - - if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) { - // If the offset hasn't changed and we're using an interpolated scroll - // then we need to keep any dependent views updated. CoL will do this for - // us when we move, but we need to do it manually when we don't (as an - // interpolated scroll may finish early). - coordinatorLayout.dispatchDependentViewsChanged(appBarLayout); - } - - // Dispatch the updates to any listeners - appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset()); - - // Update the AppBarLayout's drawable state (for any elevation changes) - updateAppBarLayoutDrawableState(coordinatorLayout, appBarLayout, newOffset, - newOffset < curOffset ? -1 : 1, false); - } - } else { - // Reset the offset delta - mOffsetDelta = 0; - } - - return consumed; - } - - @VisibleForTesting - boolean isOffsetAnimatorRunning() { - return mOffsetAnimator != null && mOffsetAnimator.isRunning(); - } - - private int interpolateOffset(AppBarLayout layout, final int offset) { - final int absOffset = Math.abs(offset); - - for (int i = 0, z = layout.getChildCount(); i < z; i++) { - final View child = layout.getChildAt(i); - final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams(); - final Interpolator interpolator = childLp.getScrollInterpolator(); - - if (absOffset >= child.getTop() && absOffset <= child.getBottom()) { - if (interpolator != null) { - int childScrollableHeight = 0; - final int flags = childLp.getScrollFlags(); - if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { - // We're set to scroll so add the child's height plus margin - childScrollableHeight += child.getHeight() + childLp.topMargin - + childLp.bottomMargin; - - if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { - // For a collapsing scroll, we to take the collapsed height - // into account. - childScrollableHeight -= ViewCompat.getMinimumHeight(child); - } - } - - if (ViewCompat.getFitsSystemWindows(child)) { - childScrollableHeight -= layout.getTopInset(); - } - - if (childScrollableHeight > 0) { - final int offsetForView = absOffset - child.getTop(); - final int interpolatedDiff = Math.round(childScrollableHeight * - interpolator.getInterpolation( - offsetForView / (float) childScrollableHeight)); - - return Integer.signum(offset) * (child.getTop() + interpolatedDiff); - } - } - - // If we get to here then the view on the offset isn't suitable for interpolated - // scrolling. So break out of the loop - break; - } - } - - return offset; - } - - private void updateAppBarLayoutDrawableState(final CoordinatorLayout parent, - final AppBarLayout layout, final int offset, final int direction, - final boolean forceJump) { - final View child = getAppBarChildOnOffset(layout, offset); - if (child != null) { - final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams(); - final int flags = childLp.getScrollFlags(); - boolean collapsed = false; - - if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) { - final int minHeight = ViewCompat.getMinimumHeight(child); - - if (direction > 0 && (flags & (LayoutParams.SCROLL_FLAG_ENTER_ALWAYS - | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED)) != 0) { - // We're set to enter always collapsed so we are only collapsed when - // being scrolled down, and in a collapsed offset - collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset(); - } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) { - // We're set to exit until collapsed, so any offset which results in - // the minimum height (or less) being shown is collapsed - collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset(); - } - } - - final boolean changed = layout.setCollapsedState(collapsed); - - if (Build.VERSION.SDK_INT >= 11 && (forceJump - || (changed && shouldJumpElevationState(parent, layout)))) { - // If the collapsed state changed, we may need to - // jump to the current state if we have an overlapping view - layout.jumpDrawablesToCurrentState(); - } - } - } - - private boolean shouldJumpElevationState(CoordinatorLayout parent, AppBarLayout layout) { - // We should jump the elevated state if we have a dependent scrolling view which has - // an overlapping top (i.e. overlaps us) - final List<View> dependencies = parent.getDependents(layout); - for (int i = 0, size = dependencies.size(); i < size; i++) { - final View dependency = dependencies.get(i); - final CoordinatorLayout.LayoutParams lp = - (CoordinatorLayout.LayoutParams) dependency.getLayoutParams(); - final CoordinatorLayout.Behavior behavior = lp.getBehavior(); - - if (behavior instanceof ScrollingViewBehavior) { - return ((ScrollingViewBehavior) behavior).getOverlayTop() != 0; - } - } - return false; - } - - private static View getAppBarChildOnOffset(final AppBarLayout layout, final int offset) { - final int absOffset = Math.abs(offset); - for (int i = 0, z = layout.getChildCount(); i < z; i++) { - final View child = layout.getChildAt(i); - if (absOffset >= child.getTop() && absOffset <= child.getBottom()) { - return child; - } - } - return null; - } - - @Override - int getTopBottomOffsetForScrollingSibling() { - return getTopAndBottomOffset() + mOffsetDelta; - } - - @Override - public Parcelable onSaveInstanceState(CoordinatorLayout parent, AppBarLayout abl) { - final Parcelable superState = super.onSaveInstanceState(parent, abl); - final int offset = getTopAndBottomOffset(); - - // Try and find the first visible child... - for (int i = 0, count = abl.getChildCount(); i < count; i++) { - View child = abl.getChildAt(i); - final int visBottom = child.getBottom() + offset; - - if (child.getTop() + offset <= 0 && visBottom >= 0) { - final SavedState ss = new SavedState(superState); - ss.firstVisibleChildIndex = i; - ss.firstVisibleChildAtMinimumHeight = - visBottom == (ViewCompat.getMinimumHeight(child) + abl.getTopInset()); - ss.firstVisibleChildPercentageShown = visBottom / (float) child.getHeight(); - return ss; - } - } - - // Else we'll just return the super state - return superState; - } - - @Override - public void onRestoreInstanceState(CoordinatorLayout parent, AppBarLayout appBarLayout, - Parcelable state) { - if (state instanceof SavedState) { - final SavedState ss = (SavedState) state; - super.onRestoreInstanceState(parent, appBarLayout, ss.getSuperState()); - mOffsetToChildIndexOnLayout = ss.firstVisibleChildIndex; - mOffsetToChildIndexOnLayoutPerc = ss.firstVisibleChildPercentageShown; - mOffsetToChildIndexOnLayoutIsMinHeight = ss.firstVisibleChildAtMinimumHeight; - } else { - super.onRestoreInstanceState(parent, appBarLayout, state); - mOffsetToChildIndexOnLayout = INVALID_POSITION; - } - } - - protected static class SavedState extends AbsSavedState { - int firstVisibleChildIndex; - float firstVisibleChildPercentageShown; - boolean firstVisibleChildAtMinimumHeight; - - public SavedState(Parcel source, ClassLoader loader) { - super(source, loader); - firstVisibleChildIndex = source.readInt(); - firstVisibleChildPercentageShown = source.readFloat(); - firstVisibleChildAtMinimumHeight = source.readByte() != 0; - } - - public SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(firstVisibleChildIndex); - dest.writeFloat(firstVisibleChildPercentageShown); - dest.writeByte((byte) (firstVisibleChildAtMinimumHeight ? 1 : 0)); - } - - public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel source, ClassLoader loader) { - return new SavedState(source, loader); - } - - @Override - public SavedState createFromParcel(Parcel source) { - return new SavedState(source, null); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - } - - /** - * Behavior which should be used by {@link View}s which can scroll vertically and support - * nested scrolling to automatically scroll any {@link AppBarLayout} siblings. - */ - public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior { - - public ScrollingViewBehavior() {} - - public ScrollingViewBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.ScrollingViewBehavior_Layout); - setOverlayTop(a.getDimensionPixelSize( - R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0)); - a.recycle(); - } - - @Override - public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { - // We depend on any AppBarLayouts - return dependency instanceof AppBarLayout; - } - - @Override - public boolean onDependentViewChanged(CoordinatorLayout parent, View child, - View dependency) { - offsetChildAsNeeded(parent, child, dependency); - return false; - } - - @Override - public boolean onRequestChildRectangleOnScreen(CoordinatorLayout parent, View child, - Rect rectangle, boolean immediate) { - final AppBarLayout header = findFirstDependency(parent.getDependencies(child)); - if (header != null) { - // Offset the rect by the child's left/top - rectangle.offset(child.getLeft(), child.getTop()); - - final Rect parentRect = mTempRect1; - parentRect.set(0, 0, parent.getWidth(), parent.getHeight()); - - if (!parentRect.contains(rectangle)) { - // If the rectangle can not be fully seen the visible bounds, collapse - // the AppBarLayout - header.setExpanded(false, !immediate); - return true; - } - } - return false; - } - - private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) { - final CoordinatorLayout.Behavior behavior = - ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior(); - if (behavior instanceof Behavior) { - // Offset the child, pinning it to the bottom the header-dependency, maintaining - // any vertical gap and overlap - final Behavior ablBehavior = (Behavior) behavior; - ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop()) - + ablBehavior.mOffsetDelta - + getVerticalLayoutGap() - - getOverlapPixelsForOffset(dependency)); - } - } - - @Override - float getOverlapRatioForOffset(final View header) { - if (header instanceof AppBarLayout) { - final AppBarLayout abl = (AppBarLayout) header; - final int totalScrollRange = abl.getTotalScrollRange(); - final int preScrollDown = abl.getDownNestedPreScrollRange(); - final int offset = getAppBarLayoutOffset(abl); - - if (preScrollDown != 0 && (totalScrollRange + offset) <= preScrollDown) { - // If we're in a pre-scroll down. Don't use the offset at all. - return 0; - } else { - final int availScrollRange = totalScrollRange - preScrollDown; - if (availScrollRange != 0) { - // Else we'll use a interpolated ratio of the overlap, depending on offset - return 1f + (offset / (float) availScrollRange); - } - } - } - return 0f; - } - - private static int getAppBarLayoutOffset(AppBarLayout abl) { - final CoordinatorLayout.Behavior behavior = - ((CoordinatorLayout.LayoutParams) abl.getLayoutParams()).getBehavior(); - if (behavior instanceof Behavior) { - return ((Behavior) behavior).getTopBottomOffsetForScrollingSibling(); - } - return 0; - } - - @Override - AppBarLayout findFirstDependency(List<View> views) { - for (int i = 0, z = views.size(); i < z; i++) { - View view = views.get(i); - if (view instanceof AppBarLayout) { - return (AppBarLayout) view; - } - } - return null; - } - - @Override - int getScrollRange(View v) { - if (v instanceof AppBarLayout) { - return ((AppBarLayout) v).getTotalScrollRange(); - } else { - return super.getScrollRange(v); - } - } - } -} diff --git a/android/support/design/widget/BaseTransientBottomBar.java b/android/support/design/widget/BaseTransientBottomBar.java deleted file mode 100644 index 18c9ef9d..00000000 --- a/android/support/design/widget/BaseTransientBottomBar.java +++ /dev/null @@ -1,753 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import static android.support.design.widget.AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.annotation.IntDef; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.design.R; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.WindowInsetsCompat; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.FrameLayout; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; - -/** - * Base class for lightweight transient bars that are displayed along the bottom edge of the - * application window. - * - * @param <B> The transient bottom bar subclass. - */ -public abstract class BaseTransientBottomBar<B extends BaseTransientBottomBar<B>> { - /** - * Base class for {@link BaseTransientBottomBar} callbacks. - * - * @param <B> The transient bottom bar subclass. - * @see BaseTransientBottomBar#addCallback(BaseCallback) - */ - public abstract static class BaseCallback<B> { - /** Indicates that the Snackbar was dismissed via a swipe.*/ - public static final int DISMISS_EVENT_SWIPE = 0; - /** Indicates that the Snackbar was dismissed via an action click.*/ - public static final int DISMISS_EVENT_ACTION = 1; - /** Indicates that the Snackbar was dismissed via a timeout.*/ - public static final int DISMISS_EVENT_TIMEOUT = 2; - /** Indicates that the Snackbar was dismissed via a call to {@link #dismiss()}.*/ - public static final int DISMISS_EVENT_MANUAL = 3; - /** Indicates that the Snackbar was dismissed from a new Snackbar being shown.*/ - public static final int DISMISS_EVENT_CONSECUTIVE = 4; - - /** @hide */ - @RestrictTo(LIBRARY_GROUP) - @IntDef({DISMISS_EVENT_SWIPE, DISMISS_EVENT_ACTION, DISMISS_EVENT_TIMEOUT, - DISMISS_EVENT_MANUAL, DISMISS_EVENT_CONSECUTIVE}) - @Retention(RetentionPolicy.SOURCE) - public @interface DismissEvent {} - - /** - * Called when the given {@link BaseTransientBottomBar} has been dismissed, either - * through a time-out, having been manually dismissed, or an action being clicked. - * - * @param transientBottomBar The transient bottom bar which has been dismissed. - * @param event The event which caused the dismissal. One of either: - * {@link #DISMISS_EVENT_SWIPE}, {@link #DISMISS_EVENT_ACTION}, - * {@link #DISMISS_EVENT_TIMEOUT}, {@link #DISMISS_EVENT_MANUAL} or - * {@link #DISMISS_EVENT_CONSECUTIVE}. - * - * @see BaseTransientBottomBar#dismiss() - */ - public void onDismissed(B transientBottomBar, @DismissEvent int event) { - // empty - } - - /** - * Called when the given {@link BaseTransientBottomBar} is visible. - * - * @param transientBottomBar The transient bottom bar which is now visible. - * @see BaseTransientBottomBar#show() - */ - public void onShown(B transientBottomBar) { - // empty - } - } - - /** - * Interface that defines the behavior of the main content of a transient bottom bar. - */ - public interface ContentViewCallback { - /** - * Animates the content of the transient bottom bar in. - * - * @param delay Animation delay. - * @param duration Animation duration. - */ - void animateContentIn(int delay, int duration); - - /** - * Animates the content of the transient bottom bar out. - * - * @param delay Animation delay. - * @param duration Animation duration. - */ - void animateContentOut(int delay, int duration); - } - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG}) - @IntRange(from = 1) - @Retention(RetentionPolicy.SOURCE) - public @interface Duration {} - - /** - * Show the Snackbar indefinitely. This means that the Snackbar will be displayed from the time - * that is {@link #show() shown} until either it is dismissed, or another Snackbar is shown. - * - * @see #setDuration - */ - public static final int LENGTH_INDEFINITE = -2; - - /** - * Show the Snackbar for a short period of time. - * - * @see #setDuration - */ - public static final int LENGTH_SHORT = -1; - - /** - * Show the Snackbar for a long period of time. - * - * @see #setDuration - */ - public static final int LENGTH_LONG = 0; - - static final int ANIMATION_DURATION = 250; - static final int ANIMATION_FADE_DURATION = 180; - - static final Handler sHandler; - static final int MSG_SHOW = 0; - static final int MSG_DISMISS = 1; - - // On JB/KK versions of the platform sometimes View.setTranslationY does not - // result in layout / draw pass, and CoordinatorLayout relies on a draw pass to - // happen to sync vertical positioning of all its child views - private static final boolean USE_OFFSET_API = (Build.VERSION.SDK_INT >= 16) - && (Build.VERSION.SDK_INT <= 19); - - static { - sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case MSG_SHOW: - ((BaseTransientBottomBar) message.obj).showView(); - return true; - case MSG_DISMISS: - ((BaseTransientBottomBar) message.obj).hideView(message.arg1); - return true; - } - return false; - } - }); - } - - private final ViewGroup mTargetParent; - private final Context mContext; - final SnackbarBaseLayout mView; - private final ContentViewCallback mContentViewCallback; - private int mDuration; - - private List<BaseCallback<B>> mCallbacks; - - private final AccessibilityManager mAccessibilityManager; - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - interface OnLayoutChangeListener { - void onLayoutChange(View view, int left, int top, int right, int bottom); - } - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - interface OnAttachStateChangeListener { - void onViewAttachedToWindow(View v); - void onViewDetachedFromWindow(View v); - } - - /** - * Constructor for the transient bottom bar. - * - * @param parent The parent for this transient bottom bar. - * @param content The content view for this transient bottom bar. - * @param contentViewCallback The content view callback for this transient bottom bar. - */ - protected BaseTransientBottomBar(@NonNull ViewGroup parent, @NonNull View content, - @NonNull ContentViewCallback contentViewCallback) { - if (parent == null) { - throw new IllegalArgumentException("Transient bottom bar must have non-null parent"); - } - if (content == null) { - throw new IllegalArgumentException("Transient bottom bar must have non-null content"); - } - if (contentViewCallback == null) { - throw new IllegalArgumentException("Transient bottom bar must have non-null callback"); - } - - mTargetParent = parent; - mContentViewCallback = contentViewCallback; - mContext = parent.getContext(); - - ThemeUtils.checkAppCompatTheme(mContext); - - LayoutInflater inflater = LayoutInflater.from(mContext); - // Note that for backwards compatibility reasons we inflate a layout that is defined - // in the extending Snackbar class. This is to prevent breakage of apps that have custom - // coordinator layout behaviors that depend on that layout. - mView = (SnackbarBaseLayout) inflater.inflate( - R.layout.design_layout_snackbar, mTargetParent, false); - mView.addView(content); - - ViewCompat.setAccessibilityLiveRegion(mView, - ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE); - ViewCompat.setImportantForAccessibility(mView, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - - // Make sure that we fit system windows and have a listener to apply any insets - ViewCompat.setFitsSystemWindows(mView, true); - ViewCompat.setOnApplyWindowInsetsListener(mView, - new android.support.v4.view.OnApplyWindowInsetsListener() { - @Override - public WindowInsetsCompat onApplyWindowInsets(View v, - WindowInsetsCompat insets) { - // Copy over the bottom inset as padding so that we're displayed - // above the navigation bar - v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), - v.getPaddingRight(), insets.getSystemWindowInsetBottom()); - return insets; - } - }); - - mAccessibilityManager = (AccessibilityManager) - mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); - } - - /** - * Set how long to show the view for. - * - * @param duration either be one of the predefined lengths: - * {@link #LENGTH_SHORT}, {@link #LENGTH_LONG}, or a custom duration - * in milliseconds. - */ - @NonNull - public B setDuration(@Duration int duration) { - mDuration = duration; - return (B) this; - } - - /** - * Return the duration. - * - * @see #setDuration - */ - @Duration - public int getDuration() { - return mDuration; - } - - /** - * Returns the {@link BaseTransientBottomBar}'s context. - */ - @NonNull - public Context getContext() { - return mContext; - } - - /** - * Returns the {@link BaseTransientBottomBar}'s view. - */ - @NonNull - public View getView() { - return mView; - } - - /** - * Show the {@link BaseTransientBottomBar}. - */ - public void show() { - SnackbarManager.getInstance().show(mDuration, mManagerCallback); - } - - /** - * Dismiss the {@link BaseTransientBottomBar}. - */ - public void dismiss() { - dispatchDismiss(BaseCallback.DISMISS_EVENT_MANUAL); - } - - void dispatchDismiss(@BaseCallback.DismissEvent int event) { - SnackbarManager.getInstance().dismiss(mManagerCallback, event); - } - - /** - * Adds the specified callback to the list of callbacks that will be notified of transient - * bottom bar events. - * - * @param callback Callback to notify when transient bottom bar events occur. - * @see #removeCallback(BaseCallback) - */ - @NonNull - public B addCallback(@NonNull BaseCallback<B> callback) { - if (callback == null) { - return (B) this; - } - if (mCallbacks == null) { - mCallbacks = new ArrayList<BaseCallback<B>>(); - } - mCallbacks.add(callback); - return (B) this; - } - - /** - * Removes the specified callback from the list of callbacks that will be notified of transient - * bottom bar events. - * - * @param callback Callback to remove from being notified of transient bottom bar events - * @see #addCallback(BaseCallback) - */ - @NonNull - public B removeCallback(@NonNull BaseCallback<B> callback) { - if (callback == null) { - return (B) this; - } - if (mCallbacks == null) { - // This can happen if this method is called before the first call to addCallback - return (B) this; - } - mCallbacks.remove(callback); - return (B) this; - } - - /** - * Return whether this {@link BaseTransientBottomBar} is currently being shown. - */ - public boolean isShown() { - return SnackbarManager.getInstance().isCurrent(mManagerCallback); - } - - /** - * Returns whether this {@link BaseTransientBottomBar} is currently being shown, or is queued - * to be shown next. - */ - public boolean isShownOrQueued() { - return SnackbarManager.getInstance().isCurrentOrNext(mManagerCallback); - } - - final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() { - @Override - public void show() { - sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, BaseTransientBottomBar.this)); - } - - @Override - public void dismiss(int event) { - sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, - BaseTransientBottomBar.this)); - } - }; - - final void showView() { - if (mView.getParent() == null) { - final ViewGroup.LayoutParams lp = mView.getLayoutParams(); - - if (lp instanceof CoordinatorLayout.LayoutParams) { - // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior - final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp; - - final Behavior behavior = new Behavior(); - behavior.setStartAlphaSwipeDistance(0.1f); - behavior.setEndAlphaSwipeDistance(0.6f); - behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END); - behavior.setListener(new SwipeDismissBehavior.OnDismissListener() { - @Override - public void onDismiss(View view) { - view.setVisibility(View.GONE); - dispatchDismiss(BaseCallback.DISMISS_EVENT_SWIPE); - } - - @Override - public void onDragStateChanged(int state) { - switch (state) { - case SwipeDismissBehavior.STATE_DRAGGING: - case SwipeDismissBehavior.STATE_SETTLING: - // If the view is being dragged or settling, pause the timeout - SnackbarManager.getInstance().pauseTimeout(mManagerCallback); - break; - case SwipeDismissBehavior.STATE_IDLE: - // If the view has been released and is idle, restore the timeout - SnackbarManager.getInstance() - .restoreTimeoutIfPaused(mManagerCallback); - break; - } - } - }); - clp.setBehavior(behavior); - // Also set the inset edge so that views can dodge the bar correctly - clp.insetEdge = Gravity.BOTTOM; - } - - mTargetParent.addView(mView); - } - - mView.setOnAttachStateChangeListener( - new BaseTransientBottomBar.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) {} - - @Override - public void onViewDetachedFromWindow(View v) { - if (isShownOrQueued()) { - // If we haven't already been dismissed then this event is coming from a - // non-user initiated action. Hence we need to make sure that we callback - // and keep our state up to date. We need to post the call since - // removeView() will call through to onDetachedFromWindow and thus overflow. - sHandler.post(new Runnable() { - @Override - public void run() { - onViewHidden(BaseCallback.DISMISS_EVENT_MANUAL); - } - }); - } - } - }); - - if (ViewCompat.isLaidOut(mView)) { - if (shouldAnimate()) { - // If animations are enabled, animate it in - animateViewIn(); - } else { - // Else if anims are disabled just call back now - onViewShown(); - } - } else { - // Otherwise, add one of our layout change listeners and show it in when laid out - mView.setOnLayoutChangeListener(new BaseTransientBottomBar.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View view, int left, int top, int right, int bottom) { - mView.setOnLayoutChangeListener(null); - - if (shouldAnimate()) { - // If animations are enabled, animate it in - animateViewIn(); - } else { - // Else if anims are disabled just call back now - onViewShown(); - } - } - }); - } - } - - void animateViewIn() { - if (Build.VERSION.SDK_INT >= 12) { - final int viewHeight = mView.getHeight(); - if (USE_OFFSET_API) { - ViewCompat.offsetTopAndBottom(mView, viewHeight); - } else { - mView.setTranslationY(viewHeight); - } - final ValueAnimator animator = new ValueAnimator(); - animator.setIntValues(viewHeight, 0); - animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); - animator.setDuration(ANIMATION_DURATION); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - mContentViewCallback.animateContentIn( - ANIMATION_DURATION - ANIMATION_FADE_DURATION, - ANIMATION_FADE_DURATION); - } - - @Override - public void onAnimationEnd(Animator animator) { - onViewShown(); - } - }); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - private int mPreviousAnimatedIntValue = viewHeight; - - @Override - public void onAnimationUpdate(ValueAnimator animator) { - int currentAnimatedIntValue = (int) animator.getAnimatedValue(); - if (USE_OFFSET_API) { - ViewCompat.offsetTopAndBottom(mView, - currentAnimatedIntValue - mPreviousAnimatedIntValue); - } else { - mView.setTranslationY(currentAnimatedIntValue); - } - mPreviousAnimatedIntValue = currentAnimatedIntValue; - } - }); - animator.start(); - } else { - final Animation anim = AnimationUtils.loadAnimation(mView.getContext(), - R.anim.design_snackbar_in); - anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); - anim.setDuration(ANIMATION_DURATION); - anim.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationEnd(Animation animation) { - onViewShown(); - } - - @Override - public void onAnimationStart(Animation animation) {} - - @Override - public void onAnimationRepeat(Animation animation) {} - }); - mView.startAnimation(anim); - } - } - - private void animateViewOut(final int event) { - if (Build.VERSION.SDK_INT >= 12) { - final ValueAnimator animator = new ValueAnimator(); - animator.setIntValues(0, mView.getHeight()); - animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); - animator.setDuration(ANIMATION_DURATION); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - mContentViewCallback.animateContentOut(0, ANIMATION_FADE_DURATION); - } - - @Override - public void onAnimationEnd(Animator animator) { - onViewHidden(event); - } - }); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - private int mPreviousAnimatedIntValue = 0; - - @Override - public void onAnimationUpdate(ValueAnimator animator) { - int currentAnimatedIntValue = (int) animator.getAnimatedValue(); - if (USE_OFFSET_API) { - ViewCompat.offsetTopAndBottom(mView, - currentAnimatedIntValue - mPreviousAnimatedIntValue); - } else { - mView.setTranslationY(currentAnimatedIntValue); - } - mPreviousAnimatedIntValue = currentAnimatedIntValue; - } - }); - animator.start(); - } else { - final Animation anim = AnimationUtils.loadAnimation(mView.getContext(), - R.anim.design_snackbar_out); - anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); - anim.setDuration(ANIMATION_DURATION); - anim.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationEnd(Animation animation) { - onViewHidden(event); - } - - @Override - public void onAnimationStart(Animation animation) {} - - @Override - public void onAnimationRepeat(Animation animation) {} - }); - mView.startAnimation(anim); - } - } - - final void hideView(@BaseCallback.DismissEvent final int event) { - if (shouldAnimate() && mView.getVisibility() == View.VISIBLE) { - animateViewOut(event); - } else { - // If anims are disabled or the view isn't visible, just call back now - onViewHidden(event); - } - } - - void onViewShown() { - SnackbarManager.getInstance().onShown(mManagerCallback); - if (mCallbacks != null) { - // Notify the callbacks. Do that from the end of the list so that if a callback - // removes itself as the result of being called, it won't mess up with our iteration - int callbackCount = mCallbacks.size(); - for (int i = callbackCount - 1; i >= 0; i--) { - mCallbacks.get(i).onShown((B) this); - } - } - } - - void onViewHidden(int event) { - // First tell the SnackbarManager that it has been dismissed - SnackbarManager.getInstance().onDismissed(mManagerCallback); - if (mCallbacks != null) { - // Notify the callbacks. Do that from the end of the list so that if a callback - // removes itself as the result of being called, it won't mess up with our iteration - int callbackCount = mCallbacks.size(); - for (int i = callbackCount - 1; i >= 0; i--) { - mCallbacks.get(i).onDismissed((B) this, event); - } - } - if (Build.VERSION.SDK_INT < 11) { - // We need to hide the Snackbar on pre-v11 since it uses an old style Animation. - // ViewGroup has special handling in removeView() when getAnimation() != null in - // that it waits. This then means that the calculated insets are wrong and the - // any dodging views do not return. We workaround it by setting the view to gone while - // ViewGroup actually gets around to removing it. - mView.setVisibility(View.GONE); - } - // Lastly, hide and remove the view from the parent (if attached) - final ViewParent parent = mView.getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).removeView(mView); - } - } - - /** - * Returns true if we should animate the Snackbar view in/out. - */ - boolean shouldAnimate() { - return !mAccessibilityManager.isEnabled(); - } - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - static class SnackbarBaseLayout extends FrameLayout { - private BaseTransientBottomBar.OnLayoutChangeListener mOnLayoutChangeListener; - private BaseTransientBottomBar.OnAttachStateChangeListener mOnAttachStateChangeListener; - - SnackbarBaseLayout(Context context) { - this(context, null); - } - - SnackbarBaseLayout(Context context, AttributeSet attrs) { - super(context, attrs); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout); - if (a.hasValue(R.styleable.SnackbarLayout_elevation)) { - ViewCompat.setElevation(this, a.getDimensionPixelSize( - R.styleable.SnackbarLayout_elevation, 0)); - } - a.recycle(); - - setClickable(true); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - if (mOnLayoutChangeListener != null) { - mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mOnAttachStateChangeListener != null) { - mOnAttachStateChangeListener.onViewAttachedToWindow(this); - } - - ViewCompat.requestApplyInsets(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mOnAttachStateChangeListener != null) { - mOnAttachStateChangeListener.onViewDetachedFromWindow(this); - } - } - - void setOnLayoutChangeListener( - BaseTransientBottomBar.OnLayoutChangeListener onLayoutChangeListener) { - mOnLayoutChangeListener = onLayoutChangeListener; - } - - void setOnAttachStateChangeListener( - BaseTransientBottomBar.OnAttachStateChangeListener listener) { - mOnAttachStateChangeListener = listener; - } - } - - final class Behavior extends SwipeDismissBehavior<SnackbarBaseLayout> { - @Override - public boolean canSwipeDismissView(View child) { - return child instanceof SnackbarBaseLayout; - } - - @Override - public boolean onInterceptTouchEvent(CoordinatorLayout parent, SnackbarBaseLayout child, - MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - // We want to make sure that we disable any Snackbar timeouts if the user is - // currently touching the Snackbar. We restore the timeout when complete - if (parent.isPointInChildBounds(child, (int) event.getX(), - (int) event.getY())) { - SnackbarManager.getInstance().pauseTimeout(mManagerCallback); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - SnackbarManager.getInstance().restoreTimeoutIfPaused(mManagerCallback); - break; - } - return super.onInterceptTouchEvent(parent, child, event); - } - } -} diff --git a/android/support/design/widget/BottomNavigationView.java b/android/support/design/widget/BottomNavigationView.java deleted file mode 100644 index 61dba876..00000000 --- a/android/support/design/widget/BottomNavigationView.java +++ /dev/null @@ -1,477 +0,0 @@ -/* - * Copyright (C) 2016 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.content.Context; -import android.content.res.ColorStateList; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.DrawableRes; -import android.support.annotation.IdRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.R; -import android.support.design.internal.BottomNavigationMenu; -import android.support.design.internal.BottomNavigationMenuView; -import android.support.design.internal.BottomNavigationPresenter; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.AbsSavedState; -import android.support.v4.view.ViewCompat; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.view.SupportMenuInflater; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.widget.TintTypedArray; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -/** - * <p> - * Represents a standard bottom navigation bar for application. It is an implementation of - * <a href="https://material.google.com/components/bottom-navigation.html">material design bottom - * navigation</a>. - * </p> - * - * <p> - * Bottom navigation bars make it easy for users to explore and switch between top-level views in - * a single tap. It should be used when application has three to five top-level destinations. - * </p> - * - * <p> - * The bar contents can be populated by specifying a menu resource file. Each menu item title, icon - * and enabled state will be used for displaying bottom navigation bar items. Menu items can also be - * used for programmatically selecting which destination is currently active. It can be done using - * {@code MenuItem#setChecked(true)} - * </p> - * - * <pre> - * layout resource file: - * <android.support.design.widget.BottomNavigationView - * xmlns:android="http://schemas.android.com/apk/res/android" - * xmlns:app="http://schemas.android.com/apk/res-auto" - * android:id="@+id/navigation" - * android:layout_width="match_parent" - * android:layout_height="56dp" - * android:layout_gravity="start" - * app:menu="@menu/my_navigation_items" /> - * - * res/menu/my_navigation_items.xml: - * <menu xmlns:android="http://schemas.android.com/apk/res/android"> - * <item android:id="@+id/action_search" - * android:title="@string/menu_search" - * android:icon="@drawable/ic_search" /> - * <item android:id="@+id/action_settings" - * android:title="@string/menu_settings" - * android:icon="@drawable/ic_add" /> - * <item android:id="@+id/action_navigation" - * android:title="@string/menu_navigation" - * android:icon="@drawable/ic_action_navigation_menu" /> - * </menu> - * </pre> - */ -public class BottomNavigationView extends FrameLayout { - - private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; - private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled}; - - private static final int MENU_PRESENTER_ID = 1; - - private final MenuBuilder mMenu; - private final BottomNavigationMenuView mMenuView; - private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter(); - private MenuInflater mMenuInflater; - - private OnNavigationItemSelectedListener mSelectedListener; - private OnNavigationItemReselectedListener mReselectedListener; - - public BottomNavigationView(Context context) { - this(context, null); - } - - public BottomNavigationView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - ThemeUtils.checkAppCompatTheme(context); - - // Create the menu - mMenu = new BottomNavigationMenu(context); - - mMenuView = new BottomNavigationMenuView(context); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.gravity = Gravity.CENTER; - mMenuView.setLayoutParams(params); - - mPresenter.setBottomNavigationMenuView(mMenuView); - mPresenter.setId(MENU_PRESENTER_ID); - mMenuView.setPresenter(mPresenter); - mMenu.addMenuPresenter(mPresenter); - mPresenter.initForMenu(getContext(), mMenu); - - // Custom attributes - TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, - R.styleable.BottomNavigationView, defStyleAttr, - R.style.Widget_Design_BottomNavigationView); - - if (a.hasValue(R.styleable.BottomNavigationView_itemIconTint)) { - mMenuView.setIconTintList( - a.getColorStateList(R.styleable.BottomNavigationView_itemIconTint)); - } else { - mMenuView.setIconTintList( - createDefaultColorStateList(android.R.attr.textColorSecondary)); - } - if (a.hasValue(R.styleable.BottomNavigationView_itemTextColor)) { - mMenuView.setItemTextColor( - a.getColorStateList(R.styleable.BottomNavigationView_itemTextColor)); - } else { - mMenuView.setItemTextColor( - createDefaultColorStateList(android.R.attr.textColorSecondary)); - } - if (a.hasValue(R.styleable.BottomNavigationView_elevation)) { - ViewCompat.setElevation(this, a.getDimensionPixelSize( - R.styleable.BottomNavigationView_elevation, 0)); - } - - int itemBackground = a.getResourceId(R.styleable.BottomNavigationView_itemBackground, 0); - mMenuView.setItemBackgroundRes(itemBackground); - - if (a.hasValue(R.styleable.BottomNavigationView_menu)) { - inflateMenu(a.getResourceId(R.styleable.BottomNavigationView_menu, 0)); - } - a.recycle(); - - addView(mMenuView, params); - if (Build.VERSION.SDK_INT < 21) { - addCompatibilityTopDivider(context); - } - - mMenu.setCallback(new MenuBuilder.Callback() { - @Override - public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { - if (mReselectedListener != null && item.getItemId() == getSelectedItemId()) { - mReselectedListener.onNavigationItemReselected(item); - return true; // item is already selected - } - return mSelectedListener != null - && !mSelectedListener.onNavigationItemSelected(item); - } - - @Override - public void onMenuModeChange(MenuBuilder menu) {} - }); - } - - /** - * Set a listener that will be notified when a bottom navigation item is selected. This listener - * will also be notified when the currently selected item is reselected, unless an - * {@link OnNavigationItemReselectedListener} has also been set. - * - * @param listener The listener to notify - * - * @see #setOnNavigationItemReselectedListener(OnNavigationItemReselectedListener) - */ - public void setOnNavigationItemSelectedListener( - @Nullable OnNavigationItemSelectedListener listener) { - mSelectedListener = listener; - } - - /** - * Set a listener that will be notified when the currently selected bottom navigation item is - * reselected. This does not require an {@link OnNavigationItemSelectedListener} to be set. - * - * @param listener The listener to notify - * - * @see #setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener) - */ - public void setOnNavigationItemReselectedListener( - @Nullable OnNavigationItemReselectedListener listener) { - mReselectedListener = listener; - } - - /** - * Returns the {@link Menu} instance associated with this bottom navigation bar. - */ - @NonNull - public Menu getMenu() { - return mMenu; - } - - /** - * Inflate a menu resource into this navigation view. - * - * <p>Existing items in the menu will not be modified or removed.</p> - * - * @param resId ID of a menu resource to inflate - */ - public void inflateMenu(int resId) { - mPresenter.setUpdateSuspended(true); - getMenuInflater().inflate(resId, mMenu); - mPresenter.setUpdateSuspended(false); - mPresenter.updateMenuView(true); - } - - /** - * @return The maximum number of items that can be shown in BottomNavigationView. - */ - public int getMaxItemCount() { - return BottomNavigationMenu.MAX_ITEM_COUNT; - } - - /** - * Returns the tint which is applied to our menu items' icons. - * - * @see #setItemIconTintList(ColorStateList) - * - * @attr ref R.styleable#BottomNavigationView_itemIconTint - */ - @Nullable - public ColorStateList getItemIconTintList() { - return mMenuView.getIconTintList(); - } - - /** - * Set the tint which is applied to our menu items' icons. - * - * @param tint the tint to apply. - * - * @attr ref R.styleable#BottomNavigationView_itemIconTint - */ - public void setItemIconTintList(@Nullable ColorStateList tint) { - mMenuView.setIconTintList(tint); - } - - /** - * Returns colors used for the different states (normal, selected, focused, etc.) of the menu - * item text. - * - * @see #setItemTextColor(ColorStateList) - * - * @return the ColorStateList of colors used for the different states of the menu items text. - * - * @attr ref R.styleable#BottomNavigationView_itemTextColor - */ - @Nullable - public ColorStateList getItemTextColor() { - return mMenuView.getItemTextColor(); - } - - /** - * Set the colors to use for the different states (normal, selected, focused, etc.) of the menu - * item text. - * - * @see #getItemTextColor() - * - * @attr ref R.styleable#BottomNavigationView_itemTextColor - */ - public void setItemTextColor(@Nullable ColorStateList textColor) { - mMenuView.setItemTextColor(textColor); - } - - /** - * Returns the background resource of the menu items. - * - * @see #setItemBackgroundResource(int) - * - * @attr ref R.styleable#BottomNavigationView_itemBackground - */ - @DrawableRes - public int getItemBackgroundResource() { - return mMenuView.getItemBackgroundRes(); - } - - /** - * Set the background of our menu items to the given resource. - * - * @param resId The identifier of the resource. - * - * @attr ref R.styleable#BottomNavigationView_itemBackground - */ - public void setItemBackgroundResource(@DrawableRes int resId) { - mMenuView.setItemBackgroundRes(resId); - } - - /** - * Returns the currently selected menu item ID, or zero if there is no menu. - * - * @see #setSelectedItemId(int) - */ - @IdRes - public int getSelectedItemId() { - return mMenuView.getSelectedItemId(); - } - - /** - * Set the selected menu item ID. This behaves the same as tapping on an item. - * - * @param itemId The menu item ID. If no item has this ID, the current selection is unchanged. - * - * @see #getSelectedItemId() - */ - public void setSelectedItemId(@IdRes int itemId) { - MenuItem item = mMenu.findItem(itemId); - if (item != null) { - if (!mMenu.performItemAction(item, mPresenter, 0)) { - item.setChecked(true); - } - } - } - - /** - * Listener for handling selection events on bottom navigation items. - */ - public interface OnNavigationItemSelectedListener { - - /** - * Called when an item in the bottom navigation menu is selected. - * - * @param item The selected item - * - * @return true to display the item as the selected item and false if the item should not - * be selected. Consider setting non-selectable items as disabled preemptively to - * make them appear non-interactive. - */ - boolean onNavigationItemSelected(@NonNull MenuItem item); - } - - /** - * Listener for handling reselection events on bottom navigation items. - */ - public interface OnNavigationItemReselectedListener { - - /** - * Called when the currently selected item in the bottom navigation menu is selected again. - * - * @param item The selected item - */ - void onNavigationItemReselected(@NonNull MenuItem item); - } - - private void addCompatibilityTopDivider(Context context) { - View divider = new View(context); - divider.setBackgroundColor( - ContextCompat.getColor(context, R.color.design_bottom_navigation_shadow_color)); - FrameLayout.LayoutParams dividerParams = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - getResources().getDimensionPixelSize( - R.dimen.design_bottom_navigation_shadow_height)); - divider.setLayoutParams(dividerParams); - addView(divider); - } - - private MenuInflater getMenuInflater() { - if (mMenuInflater == null) { - mMenuInflater = new SupportMenuInflater(getContext()); - } - return mMenuInflater; - } - - private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) { - final TypedValue value = new TypedValue(); - if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) { - return null; - } - ColorStateList baseColor = AppCompatResources.getColorStateList( - getContext(), value.resourceId); - if (!getContext().getTheme().resolveAttribute( - android.support.v7.appcompat.R.attr.colorPrimary, value, true)) { - return null; - } - int colorPrimary = value.data; - int defaultColor = baseColor.getDefaultColor(); - return new ColorStateList(new int[][]{ - DISABLED_STATE_SET, - CHECKED_STATE_SET, - EMPTY_STATE_SET - }, new int[]{ - baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), - colorPrimary, - defaultColor - }); - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState savedState = new SavedState(superState); - savedState.menuPresenterState = new Bundle(); - mMenu.savePresenterStates(savedState.menuPresenterState); - return savedState; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - mMenu.restorePresenterStates(savedState.menuPresenterState); - } - - static class SavedState extends AbsSavedState { - Bundle menuPresenterState; - - public SavedState(Parcelable superState) { - super(superState); - } - - public SavedState(Parcel source, ClassLoader loader) { - super(source, loader); - readFromParcel(source, loader); - } - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeBundle(menuPresenterState); - } - - private void readFromParcel(Parcel in, ClassLoader loader) { - menuPresenterState = in.readBundle(loader); - } - - public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel in, ClassLoader loader) { - return new SavedState(in, loader); - } - - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in, null); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } -} diff --git a/android/support/design/widget/BottomSheetBehavior.java b/android/support/design/widget/BottomSheetBehavior.java deleted file mode 100644 index 00ce8f90..00000000 --- a/android/support/design/widget/BottomSheetBehavior.java +++ /dev/null @@ -1,829 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.annotation.VisibleForTesting; -import android.support.design.R; -import android.support.v4.math.MathUtils; -import android.support.v4.view.AbsSavedState; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ViewDragHelper; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; - - -/** - * An interaction behavior plugin for a child view of {@link CoordinatorLayout} to make it work as - * a bottom sheet. - */ -public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> { - - /** - * Callback for monitoring events about bottom sheets. - */ - public abstract static class BottomSheetCallback { - - /** - * Called when the bottom sheet changes its state. - * - * @param bottomSheet The bottom sheet view. - * @param newState The new state. This will be one of {@link #STATE_DRAGGING}, - * {@link #STATE_SETTLING}, {@link #STATE_EXPANDED}, - * {@link #STATE_COLLAPSED}, or {@link #STATE_HIDDEN}. - */ - public abstract void onStateChanged(@NonNull View bottomSheet, @State int newState); - - /** - * Called when the bottom sheet is being dragged. - * - * @param bottomSheet The bottom sheet view. - * @param slideOffset The new offset of this bottom sheet within [-1,1] range. Offset - * increases as this bottom sheet is moving upward. From 0 to 1 the sheet - * is between collapsed and expanded states and from -1 to 0 it is - * between hidden and collapsed states. - */ - public abstract void onSlide(@NonNull View bottomSheet, float slideOffset); - } - - /** - * The bottom sheet is dragging. - */ - public static final int STATE_DRAGGING = 1; - - /** - * The bottom sheet is settling. - */ - public static final int STATE_SETTLING = 2; - - /** - * The bottom sheet is expanded. - */ - public static final int STATE_EXPANDED = 3; - - /** - * The bottom sheet is collapsed. - */ - public static final int STATE_COLLAPSED = 4; - - /** - * The bottom sheet is hidden. - */ - public static final int STATE_HIDDEN = 5; - - /** @hide */ - @RestrictTo(LIBRARY_GROUP) - @IntDef({STATE_EXPANDED, STATE_COLLAPSED, STATE_DRAGGING, STATE_SETTLING, STATE_HIDDEN}) - @Retention(RetentionPolicy.SOURCE) - public @interface State {} - - /** - * Peek at the 16:9 ratio keyline of its parent. - * - * <p>This can be used as a parameter for {@link #setPeekHeight(int)}. - * {@link #getPeekHeight()} will return this when the value is set.</p> - */ - public static final int PEEK_HEIGHT_AUTO = -1; - - private static final float HIDE_THRESHOLD = 0.5f; - - private static final float HIDE_FRICTION = 0.1f; - - private float mMaximumVelocity; - - private int mPeekHeight; - - private boolean mPeekHeightAuto; - - private int mPeekHeightMin; - - int mMinOffset; - - int mMaxOffset; - - boolean mHideable; - - private boolean mSkipCollapsed; - - @State - int mState = STATE_COLLAPSED; - - ViewDragHelper mViewDragHelper; - - private boolean mIgnoreEvents; - - private int mLastNestedScrollDy; - - private boolean mNestedScrolled; - - int mParentHeight; - - WeakReference<V> mViewRef; - - WeakReference<View> mNestedScrollingChildRef; - - private BottomSheetCallback mCallback; - - private VelocityTracker mVelocityTracker; - - int mActivePointerId; - - private int mInitialY; - - boolean mTouchingScrollingChild; - - /** - * Default constructor for instantiating BottomSheetBehaviors. - */ - public BottomSheetBehavior() { - } - - /** - * Default constructor for inflating BottomSheetBehaviors from layout. - * - * @param context The {@link Context}. - * @param attrs The {@link AttributeSet}. - */ - public BottomSheetBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.BottomSheetBehavior_Layout); - TypedValue value = a.peekValue(R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight); - if (value != null && value.data == PEEK_HEIGHT_AUTO) { - setPeekHeight(value.data); - } else { - setPeekHeight(a.getDimensionPixelSize( - R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight, PEEK_HEIGHT_AUTO)); - } - setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_hideable, false)); - setSkipCollapsed(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed, - false)); - a.recycle(); - ViewConfiguration configuration = ViewConfiguration.get(context); - mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - } - - @Override - public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) { - return new SavedState(super.onSaveInstanceState(parent, child), mState); - } - - @Override - public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) { - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(parent, child, ss.getSuperState()); - // Intermediate states are restored as collapsed state - if (ss.state == STATE_DRAGGING || ss.state == STATE_SETTLING) { - mState = STATE_COLLAPSED; - } else { - mState = ss.state; - } - } - - @Override - public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { - if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) { - ViewCompat.setFitsSystemWindows(child, true); - } - int savedTop = child.getTop(); - // First let the parent lay it out - parent.onLayoutChild(child, layoutDirection); - // Offset the bottom sheet - mParentHeight = parent.getHeight(); - int peekHeight; - if (mPeekHeightAuto) { - if (mPeekHeightMin == 0) { - mPeekHeightMin = parent.getResources().getDimensionPixelSize( - R.dimen.design_bottom_sheet_peek_height_min); - } - peekHeight = Math.max(mPeekHeightMin, mParentHeight - parent.getWidth() * 9 / 16); - } else { - peekHeight = mPeekHeight; - } - mMinOffset = Math.max(0, mParentHeight - child.getHeight()); - mMaxOffset = Math.max(mParentHeight - peekHeight, mMinOffset); - if (mState == STATE_EXPANDED) { - ViewCompat.offsetTopAndBottom(child, mMinOffset); - } else if (mHideable && mState == STATE_HIDDEN) { - ViewCompat.offsetTopAndBottom(child, mParentHeight); - } else if (mState == STATE_COLLAPSED) { - ViewCompat.offsetTopAndBottom(child, mMaxOffset); - } else if (mState == STATE_DRAGGING || mState == STATE_SETTLING) { - ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop()); - } - if (mViewDragHelper == null) { - mViewDragHelper = ViewDragHelper.create(parent, mDragCallback); - } - mViewRef = new WeakReference<>(child); - mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child)); - return true; - } - - @Override - public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { - if (!child.isShown()) { - mIgnoreEvents = true; - return false; - } - int action = event.getActionMasked(); - // Record the velocity - if (action == MotionEvent.ACTION_DOWN) { - reset(); - } - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - switch (action) { - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mTouchingScrollingChild = false; - mActivePointerId = MotionEvent.INVALID_POINTER_ID; - // Reset the ignore flag - if (mIgnoreEvents) { - mIgnoreEvents = false; - return false; - } - break; - case MotionEvent.ACTION_DOWN: - int initialX = (int) event.getX(); - mInitialY = (int) event.getY(); - View scroll = mNestedScrollingChildRef != null - ? mNestedScrollingChildRef.get() : null; - if (scroll != null && parent.isPointInChildBounds(scroll, initialX, mInitialY)) { - mActivePointerId = event.getPointerId(event.getActionIndex()); - mTouchingScrollingChild = true; - } - mIgnoreEvents = mActivePointerId == MotionEvent.INVALID_POINTER_ID && - !parent.isPointInChildBounds(child, initialX, mInitialY); - break; - } - if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) { - return true; - } - // We have to handle cases that the ViewDragHelper does not capture the bottom sheet because - // it is not the top most view of its parent. This is not necessary when the touch event is - // happening over the scrolling content as nested scrolling logic handles that case. - View scroll = mNestedScrollingChildRef.get(); - return action == MotionEvent.ACTION_MOVE && scroll != null && - !mIgnoreEvents && mState != STATE_DRAGGING && - !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) && - Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop(); - } - - @Override - public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { - if (!child.isShown()) { - return false; - } - int action = event.getActionMasked(); - if (mState == STATE_DRAGGING && action == MotionEvent.ACTION_DOWN) { - return true; - } - if (mViewDragHelper != null) { - mViewDragHelper.processTouchEvent(event); - } - // Record the velocity - if (action == MotionEvent.ACTION_DOWN) { - reset(); - } - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - // The ViewDragHelper tries to capture only the top-most View. We have to explicitly tell it - // to capture the bottom sheet in case it is not captured and the touch slop is passed. - if (action == MotionEvent.ACTION_MOVE && !mIgnoreEvents) { - if (Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop()) { - mViewDragHelper.captureChildView(child, event.getPointerId(event.getActionIndex())); - } - } - return !mIgnoreEvents; - } - - @Override - public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, - View directTargetChild, View target, int nestedScrollAxes) { - mLastNestedScrollDy = 0; - mNestedScrolled = false; - return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; - } - - @Override - public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, - int dy, int[] consumed) { - View scrollingChild = mNestedScrollingChildRef.get(); - if (target != scrollingChild) { - return; - } - int currentTop = child.getTop(); - int newTop = currentTop - dy; - if (dy > 0) { // Upward - if (newTop < mMinOffset) { - consumed[1] = currentTop - mMinOffset; - ViewCompat.offsetTopAndBottom(child, -consumed[1]); - setStateInternal(STATE_EXPANDED); - } else { - consumed[1] = dy; - ViewCompat.offsetTopAndBottom(child, -dy); - setStateInternal(STATE_DRAGGING); - } - } else if (dy < 0) { // Downward - if (!target.canScrollVertically(-1)) { - if (newTop <= mMaxOffset || mHideable) { - consumed[1] = dy; - ViewCompat.offsetTopAndBottom(child, -dy); - setStateInternal(STATE_DRAGGING); - } else { - consumed[1] = currentTop - mMaxOffset; - ViewCompat.offsetTopAndBottom(child, -consumed[1]); - setStateInternal(STATE_COLLAPSED); - } - } - } - dispatchOnSlide(child.getTop()); - mLastNestedScrollDy = dy; - mNestedScrolled = true; - } - - @Override - public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { - if (child.getTop() == mMinOffset) { - setStateInternal(STATE_EXPANDED); - return; - } - if (mNestedScrollingChildRef == null || target != mNestedScrollingChildRef.get() - || !mNestedScrolled) { - return; - } - int top; - int targetState; - if (mLastNestedScrollDy > 0) { - top = mMinOffset; - targetState = STATE_EXPANDED; - } else if (mHideable && shouldHide(child, getYVelocity())) { - top = mParentHeight; - targetState = STATE_HIDDEN; - } else if (mLastNestedScrollDy == 0) { - int currentTop = child.getTop(); - if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) { - top = mMinOffset; - targetState = STATE_EXPANDED; - } else { - top = mMaxOffset; - targetState = STATE_COLLAPSED; - } - } else { - top = mMaxOffset; - targetState = STATE_COLLAPSED; - } - if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { - setStateInternal(STATE_SETTLING); - ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState)); - } else { - setStateInternal(targetState); - } - mNestedScrolled = false; - } - - @Override - public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, - float velocityX, float velocityY) { - return target == mNestedScrollingChildRef.get() && - (mState != STATE_EXPANDED || - super.onNestedPreFling(coordinatorLayout, child, target, - velocityX, velocityY)); - } - - /** - * Sets the height of the bottom sheet when it is collapsed. - * - * @param peekHeight The height of the collapsed bottom sheet in pixels, or - * {@link #PEEK_HEIGHT_AUTO} to configure the sheet to peek automatically - * at 16:9 ratio keyline. - * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_peekHeight - */ - public final void setPeekHeight(int peekHeight) { - boolean layout = false; - if (peekHeight == PEEK_HEIGHT_AUTO) { - if (!mPeekHeightAuto) { - mPeekHeightAuto = true; - layout = true; - } - } else if (mPeekHeightAuto || mPeekHeight != peekHeight) { - mPeekHeightAuto = false; - mPeekHeight = Math.max(0, peekHeight); - mMaxOffset = mParentHeight - peekHeight; - layout = true; - } - if (layout && mState == STATE_COLLAPSED && mViewRef != null) { - V view = mViewRef.get(); - if (view != null) { - view.requestLayout(); - } - } - } - - /** - * Gets the height of the bottom sheet when it is collapsed. - * - * @return The height of the collapsed bottom sheet in pixels, or {@link #PEEK_HEIGHT_AUTO} - * if the sheet is configured to peek automatically at 16:9 ratio keyline - * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_peekHeight - */ - public final int getPeekHeight() { - return mPeekHeightAuto ? PEEK_HEIGHT_AUTO : mPeekHeight; - } - - /** - * Sets whether this bottom sheet can hide when it is swiped down. - * - * @param hideable {@code true} to make this bottom sheet hideable. - * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_hideable - */ - public void setHideable(boolean hideable) { - mHideable = hideable; - } - - /** - * Gets whether this bottom sheet can hide when it is swiped down. - * - * @return {@code true} if this bottom sheet can hide. - * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_hideable - */ - public boolean isHideable() { - return mHideable; - } - - /** - * Sets whether this bottom sheet should skip the collapsed state when it is being hidden - * after it is expanded once. Setting this to true has no effect unless the sheet is hideable. - * - * @param skipCollapsed True if the bottom sheet should skip the collapsed state. - * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_skipCollapsed - */ - public void setSkipCollapsed(boolean skipCollapsed) { - mSkipCollapsed = skipCollapsed; - } - - /** - * Sets whether this bottom sheet should skip the collapsed state when it is being hidden - * after it is expanded once. - * - * @return Whether the bottom sheet should skip the collapsed state. - * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_skipCollapsed - */ - public boolean getSkipCollapsed() { - return mSkipCollapsed; - } - - /** - * Sets a callback to be notified of bottom sheet events. - * - * @param callback The callback to notify when bottom sheet events occur. - */ - public void setBottomSheetCallback(BottomSheetCallback callback) { - mCallback = callback; - } - - /** - * Sets the state of the bottom sheet. The bottom sheet will transition to that state with - * animation. - * - * @param state One of {@link #STATE_COLLAPSED}, {@link #STATE_EXPANDED}, or - * {@link #STATE_HIDDEN}. - */ - public final void setState(final @State int state) { - if (state == mState) { - return; - } - if (mViewRef == null) { - // The view is not laid out yet; modify mState and let onLayoutChild handle it later - if (state == STATE_COLLAPSED || state == STATE_EXPANDED || - (mHideable && state == STATE_HIDDEN)) { - mState = state; - } - return; - } - final V child = mViewRef.get(); - if (child == null) { - return; - } - // Start the animation; wait until a pending layout if there is one. - ViewParent parent = child.getParent(); - if (parent != null && parent.isLayoutRequested() && ViewCompat.isAttachedToWindow(child)) { - child.post(new Runnable() { - @Override - public void run() { - startSettlingAnimation(child, state); - } - }); - } else { - startSettlingAnimation(child, state); - } - } - - /** - * Gets the current state of the bottom sheet. - * - * @return One of {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link #STATE_DRAGGING}, - * {@link #STATE_SETTLING}, and {@link #STATE_HIDDEN}. - */ - @State - public final int getState() { - return mState; - } - - void setStateInternal(@State int state) { - if (mState == state) { - return; - } - mState = state; - View bottomSheet = mViewRef.get(); - if (bottomSheet != null && mCallback != null) { - mCallback.onStateChanged(bottomSheet, state); - } - } - - private void reset() { - mActivePointerId = ViewDragHelper.INVALID_POINTER; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - boolean shouldHide(View child, float yvel) { - if (mSkipCollapsed) { - return true; - } - if (child.getTop() < mMaxOffset) { - // It should not hide, but collapse. - return false; - } - final float newTop = child.getTop() + yvel * HIDE_FRICTION; - return Math.abs(newTop - mMaxOffset) / (float) mPeekHeight > HIDE_THRESHOLD; - } - - @VisibleForTesting - View findScrollingChild(View view) { - if (ViewCompat.isNestedScrollingEnabled(view)) { - return view; - } - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - for (int i = 0, count = group.getChildCount(); i < count; i++) { - View scrollingChild = findScrollingChild(group.getChildAt(i)); - if (scrollingChild != null) { - return scrollingChild; - } - } - } - return null; - } - - private float getYVelocity() { - mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - return mVelocityTracker.getYVelocity(mActivePointerId); - } - - void startSettlingAnimation(View child, int state) { - int top; - if (state == STATE_COLLAPSED) { - top = mMaxOffset; - } else if (state == STATE_EXPANDED) { - top = mMinOffset; - } else if (mHideable && state == STATE_HIDDEN) { - top = mParentHeight; - } else { - throw new IllegalArgumentException("Illegal state argument: " + state); - } - if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { - setStateInternal(STATE_SETTLING); - ViewCompat.postOnAnimation(child, new SettleRunnable(child, state)); - } else { - setStateInternal(state); - } - } - - private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() { - - @Override - public boolean tryCaptureView(View child, int pointerId) { - if (mState == STATE_DRAGGING) { - return false; - } - if (mTouchingScrollingChild) { - return false; - } - if (mState == STATE_EXPANDED && mActivePointerId == pointerId) { - View scroll = mNestedScrollingChildRef.get(); - if (scroll != null && scroll.canScrollVertically(-1)) { - // Let the content scroll up - return false; - } - } - return mViewRef != null && mViewRef.get() == child; - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - dispatchOnSlide(top); - } - - @Override - public void onViewDragStateChanged(int state) { - if (state == ViewDragHelper.STATE_DRAGGING) { - setStateInternal(STATE_DRAGGING); - } - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - int top; - @State int targetState; - if (yvel < 0) { // Moving up - top = mMinOffset; - targetState = STATE_EXPANDED; - } else if (mHideable && shouldHide(releasedChild, yvel)) { - top = mParentHeight; - targetState = STATE_HIDDEN; - } else if (yvel == 0.f) { - int currentTop = releasedChild.getTop(); - if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) { - top = mMinOffset; - targetState = STATE_EXPANDED; - } else { - top = mMaxOffset; - targetState = STATE_COLLAPSED; - } - } else { - top = mMaxOffset; - targetState = STATE_COLLAPSED; - } - if (mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) { - setStateInternal(STATE_SETTLING); - ViewCompat.postOnAnimation(releasedChild, - new SettleRunnable(releasedChild, targetState)); - } else { - setStateInternal(targetState); - } - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - return MathUtils.clamp(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset); - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - return child.getLeft(); - } - - @Override - public int getViewVerticalDragRange(View child) { - if (mHideable) { - return mParentHeight - mMinOffset; - } else { - return mMaxOffset - mMinOffset; - } - } - }; - - void dispatchOnSlide(int top) { - View bottomSheet = mViewRef.get(); - if (bottomSheet != null && mCallback != null) { - if (top > mMaxOffset) { - mCallback.onSlide(bottomSheet, (float) (mMaxOffset - top) / - (mParentHeight - mMaxOffset)); - } else { - mCallback.onSlide(bottomSheet, - (float) (mMaxOffset - top) / ((mMaxOffset - mMinOffset))); - } - } - } - - @VisibleForTesting - int getPeekHeightMin() { - return mPeekHeightMin; - } - - private class SettleRunnable implements Runnable { - - private final View mView; - - @State - private final int mTargetState; - - SettleRunnable(View view, @State int targetState) { - mView = view; - mTargetState = targetState; - } - - @Override - public void run() { - if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) { - ViewCompat.postOnAnimation(mView, this); - } else { - setStateInternal(mTargetState); - } - } - } - - protected static class SavedState extends AbsSavedState { - @State - final int state; - - public SavedState(Parcel source) { - this(source, null); - } - - public SavedState(Parcel source, ClassLoader loader) { - super(source, loader); - //noinspection ResourceType - state = source.readInt(); - } - - public SavedState(Parcelable superState, @State int state) { - super(superState); - this.state = state; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeInt(state); - } - - public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel in, ClassLoader loader) { - return new SavedState(in, loader); - } - - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in, null); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - /** - * A utility function to get the {@link BottomSheetBehavior} associated with the {@code view}. - * - * @param view The {@link View} with {@link BottomSheetBehavior}. - * @return The {@link BottomSheetBehavior} associated with the {@code view}. - */ - @SuppressWarnings("unchecked") - public static <V extends View> BottomSheetBehavior<V> from(V view) { - ViewGroup.LayoutParams params = view.getLayoutParams(); - if (!(params instanceof CoordinatorLayout.LayoutParams)) { - throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); - } - CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params) - .getBehavior(); - if (!(behavior instanceof BottomSheetBehavior)) { - throw new IllegalArgumentException( - "The view is not associated with BottomSheetBehavior"); - } - return (BottomSheetBehavior<V>) behavior; - } - -} diff --git a/android/support/design/widget/BottomSheetDialog.java b/android/support/design/widget/BottomSheetDialog.java deleted file mode 100644 index 19b5782d..00000000 --- a/android/support/design/widget/BottomSheetDialog.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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.content.Context; -import android.content.res.TypedArray; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.StyleRes; -import android.support.design.R; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v7.app.AppCompatDialog; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.widget.FrameLayout; - -/** - * Base class for {@link android.app.Dialog}s styled as a bottom sheet. - */ -public class BottomSheetDialog extends AppCompatDialog { - - private BottomSheetBehavior<FrameLayout> mBehavior; - - boolean mCancelable = true; - private boolean mCanceledOnTouchOutside = true; - private boolean mCanceledOnTouchOutsideSet; - - public BottomSheetDialog(@NonNull Context context) { - this(context, 0); - } - - public BottomSheetDialog(@NonNull Context context, @StyleRes int theme) { - super(context, getThemeResId(context, theme)); - // We hide the title bar for any style configuration. Otherwise, there will be a gap - // above the bottom sheet when it is expanded. - supportRequestWindowFeature(Window.FEATURE_NO_TITLE); - } - - protected BottomSheetDialog(@NonNull Context context, boolean cancelable, - OnCancelListener cancelListener) { - super(context, cancelable, cancelListener); - supportRequestWindowFeature(Window.FEATURE_NO_TITLE); - mCancelable = cancelable; - } - - @Override - public void setContentView(@LayoutRes int layoutResId) { - super.setContentView(wrapInBottomSheet(layoutResId, null, null)); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Window window = getWindow(); - if (window != null) { - if (Build.VERSION.SDK_INT >= 21) { - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - } - window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - } - } - - @Override - public void setContentView(View view) { - super.setContentView(wrapInBottomSheet(0, view, null)); - } - - @Override - public void setContentView(View view, ViewGroup.LayoutParams params) { - super.setContentView(wrapInBottomSheet(0, view, params)); - } - - @Override - public void setCancelable(boolean cancelable) { - super.setCancelable(cancelable); - if (mCancelable != cancelable) { - mCancelable = cancelable; - if (mBehavior != null) { - mBehavior.setHideable(cancelable); - } - } - } - - @Override - protected void onStart() { - super.onStart(); - if (mBehavior != null) { - mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); - } - } - - @Override - public void setCanceledOnTouchOutside(boolean cancel) { - super.setCanceledOnTouchOutside(cancel); - if (cancel && !mCancelable) { - mCancelable = true; - } - mCanceledOnTouchOutside = cancel; - mCanceledOnTouchOutsideSet = true; - } - - private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) { - final FrameLayout container = (FrameLayout) View.inflate(getContext(), - R.layout.design_bottom_sheet_dialog, null); - final CoordinatorLayout coordinator = - (CoordinatorLayout) container.findViewById(R.id.coordinator); - if (layoutResId != 0 && view == null) { - view = getLayoutInflater().inflate(layoutResId, coordinator, false); - } - FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet); - mBehavior = BottomSheetBehavior.from(bottomSheet); - mBehavior.setBottomSheetCallback(mBottomSheetCallback); - mBehavior.setHideable(mCancelable); - if (params == null) { - bottomSheet.addView(view); - } else { - bottomSheet.addView(view, params); - } - // We treat the CoordinatorLayout as outside the dialog though it is technically inside - coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) { - cancel(); - } - } - }); - // Handle accessibility events - ViewCompat.setAccessibilityDelegate(bottomSheet, new AccessibilityDelegateCompat() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, - AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - if (mCancelable) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_DISMISS); - info.setDismissable(true); - } else { - info.setDismissable(false); - } - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (action == AccessibilityNodeInfoCompat.ACTION_DISMISS && mCancelable) { - cancel(); - return true; - } - return super.performAccessibilityAction(host, action, args); - } - }); - bottomSheet.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent event) { - // Consume the event and prevent it from falling through - return true; - } - }); - return container; - } - - boolean shouldWindowCloseOnTouchOutside() { - if (!mCanceledOnTouchOutsideSet) { - if (Build.VERSION.SDK_INT < 11) { - mCanceledOnTouchOutside = true; - } else { - TypedArray a = getContext().obtainStyledAttributes( - new int[]{android.R.attr.windowCloseOnTouchOutside}); - mCanceledOnTouchOutside = a.getBoolean(0, true); - a.recycle(); - } - mCanceledOnTouchOutsideSet = true; - } - return mCanceledOnTouchOutside; - } - - private static int getThemeResId(Context context, int themeId) { - if (themeId == 0) { - // If the provided theme is 0, then retrieve the dialogTheme from our theme - TypedValue outValue = new TypedValue(); - if (context.getTheme().resolveAttribute( - R.attr.bottomSheetDialogTheme, outValue, true)) { - themeId = outValue.resourceId; - } else { - // bottomSheetDialogTheme is not provided; we default to our light theme - themeId = R.style.Theme_Design_Light_BottomSheetDialog; - } - } - return themeId; - } - - private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback - = new BottomSheetBehavior.BottomSheetCallback() { - @Override - public void onStateChanged(@NonNull View bottomSheet, - @BottomSheetBehavior.State int newState) { - if (newState == BottomSheetBehavior.STATE_HIDDEN) { - cancel(); - } - } - - @Override - public void onSlide(@NonNull View bottomSheet, float slideOffset) { - } - }; - -} diff --git a/android/support/design/widget/BottomSheetDialogFragment.java b/android/support/design/widget/BottomSheetDialogFragment.java deleted file mode 100644 index 88429880..00000000 --- a/android/support/design/widget/BottomSheetDialogFragment.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.app.Dialog; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AppCompatDialogFragment; - -/** - * Modal bottom sheet. This is a version of {@link DialogFragment} that shows a bottom sheet - * using {@link BottomSheetDialog} instead of a floating dialog. - */ -public class BottomSheetDialogFragment extends AppCompatDialogFragment { - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new BottomSheetDialog(getContext(), getTheme()); - } - -} diff --git a/android/support/design/widget/CheckableImageButton.java b/android/support/design/widget/CheckableImageButton.java deleted file mode 100644 index f2745817..00000000 --- a/android/support/design/widget/CheckableImageButton.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2016 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.support.annotation.RestrictTo; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v7.widget.AppCompatImageButton; -import android.util.AttributeSet; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; -import android.widget.Checkable; - -/** - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class CheckableImageButton extends AppCompatImageButton implements Checkable { - - private static final int[] DRAWABLE_STATE_CHECKED = new int[]{android.R.attr.state_checked}; - - private boolean mChecked; - - public CheckableImageButton(Context context) { - this(context, null); - } - - public CheckableImageButton(Context context, AttributeSet attrs) { - this(context, attrs, android.support.v7.appcompat.R.attr.imageButtonStyle); - } - - public CheckableImageButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegateCompat() { - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - event.setChecked(isChecked()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(View host, - AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setCheckable(true); - info.setChecked(isChecked()); - } - }); - } - - @Override - public void setChecked(boolean checked) { - if (mChecked != checked) { - mChecked = checked; - refreshDrawableState(); - sendAccessibilityEvent( - AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED); - } - } - - @Override - public boolean isChecked() { - return mChecked; - } - - @Override - public void toggle() { - setChecked(!mChecked); - } - - @Override - public int[] onCreateDrawableState(int extraSpace) { - if (mChecked) { - return mergeDrawableStates( - super.onCreateDrawableState(extraSpace + DRAWABLE_STATE_CHECKED.length), - DRAWABLE_STATE_CHECKED); - } else { - return super.onCreateDrawableState(extraSpace); - } - } -} diff --git a/android/support/design/widget/CircularBorderDrawable.java b/android/support/design/widget/CircularBorderDrawable.java deleted file mode 100644 index 617a5010..00000000 --- a/android/support/design/widget/CircularBorderDrawable.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * 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.content.res.ColorStateList; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.drawable.Drawable; -import android.support.v4.graphics.ColorUtils; - -/** - * A drawable which draws an oval 'border'. - */ -class CircularBorderDrawable extends Drawable { - - /** - * We actually draw the stroke wider than the border size given. This is to reduce any - * potential transparent space caused by anti-aliasing and padding rounding. - * This value defines the multiplier used to determine to draw stroke width. - */ - private static final float DRAW_STROKE_WIDTH_MULTIPLE = 1.3333f; - - final Paint mPaint; - final Rect mRect = new Rect(); - final RectF mRectF = new RectF(); - - float mBorderWidth; - - private int mTopOuterStrokeColor; - private int mTopInnerStrokeColor; - private int mBottomOuterStrokeColor; - private int mBottomInnerStrokeColor; - - private ColorStateList mBorderTint; - private int mCurrentBorderTintColor; - - private boolean mInvalidateShader = true; - - private float mRotation; - - public CircularBorderDrawable() { - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setStyle(Paint.Style.STROKE); - } - - void setGradientColors(int topOuterStrokeColor, int topInnerStrokeColor, - int bottomOuterStrokeColor, int bottomInnerStrokeColor) { - mTopOuterStrokeColor = topOuterStrokeColor; - mTopInnerStrokeColor = topInnerStrokeColor; - mBottomOuterStrokeColor = bottomOuterStrokeColor; - mBottomInnerStrokeColor = bottomInnerStrokeColor; - } - - /** - * Set the border width - */ - void setBorderWidth(float width) { - if (mBorderWidth != width) { - mBorderWidth = width; - mPaint.setStrokeWidth(width * DRAW_STROKE_WIDTH_MULTIPLE); - mInvalidateShader = true; - invalidateSelf(); - } - } - - @Override - public void draw(Canvas canvas) { - if (mInvalidateShader) { - mPaint.setShader(createGradientShader()); - mInvalidateShader = false; - } - - final float halfBorderWidth = mPaint.getStrokeWidth() / 2f; - final RectF rectF = mRectF; - - // We need to inset the oval bounds by half the border width. This is because stroke draws - // the center of the border on the dimension. Whereas we want the stroke on the inside. - copyBounds(mRect); - rectF.set(mRect); - rectF.left += halfBorderWidth; - rectF.top += halfBorderWidth; - rectF.right -= halfBorderWidth; - rectF.bottom -= halfBorderWidth; - - canvas.save(); - canvas.rotate(mRotation, rectF.centerX(), rectF.centerY()); - // Draw the oval - canvas.drawOval(rectF, mPaint); - canvas.restore(); - } - - @Override - public boolean getPadding(Rect padding) { - final int borderWidth = Math.round(mBorderWidth); - padding.set(borderWidth, borderWidth, borderWidth, borderWidth); - return true; - } - - @Override - public void setAlpha(int alpha) { - mPaint.setAlpha(alpha); - invalidateSelf(); - } - - void setBorderTint(ColorStateList tint) { - if (tint != null) { - mCurrentBorderTintColor = tint.getColorForState(getState(), mCurrentBorderTintColor); - } - mBorderTint = tint; - mInvalidateShader = true; - invalidateSelf(); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - mPaint.setColorFilter(colorFilter); - invalidateSelf(); - } - - @Override - public int getOpacity() { - return mBorderWidth > 0 ? PixelFormat.TRANSLUCENT : PixelFormat.TRANSPARENT; - } - - final void setRotation(float rotation) { - if (rotation != mRotation) { - mRotation = rotation; - invalidateSelf(); - } - } - - @Override - protected void onBoundsChange(Rect bounds) { - mInvalidateShader = true; - } - - @Override - public boolean isStateful() { - return (mBorderTint != null && mBorderTint.isStateful()) || super.isStateful(); - } - - @Override - protected boolean onStateChange(int[] state) { - if (mBorderTint != null) { - final int newColor = mBorderTint.getColorForState(state, mCurrentBorderTintColor); - if (newColor != mCurrentBorderTintColor) { - mInvalidateShader = true; - mCurrentBorderTintColor = newColor; - } - } - if (mInvalidateShader) { - invalidateSelf(); - } - return mInvalidateShader; - } - - /** - * Creates a vertical {@link LinearGradient} - * @return - */ - private Shader createGradientShader() { - final Rect rect = mRect; - copyBounds(rect); - - final float borderRatio = mBorderWidth / rect.height(); - - final int[] colors = new int[6]; - colors[0] = ColorUtils.compositeColors(mTopOuterStrokeColor, mCurrentBorderTintColor); - colors[1] = ColorUtils.compositeColors(mTopInnerStrokeColor, mCurrentBorderTintColor); - colors[2] = ColorUtils.compositeColors( - ColorUtils.setAlphaComponent(mTopInnerStrokeColor, 0), mCurrentBorderTintColor); - colors[3] = ColorUtils.compositeColors( - ColorUtils.setAlphaComponent(mBottomInnerStrokeColor, 0), mCurrentBorderTintColor); - colors[4] = ColorUtils.compositeColors(mBottomInnerStrokeColor, mCurrentBorderTintColor); - colors[5] = ColorUtils.compositeColors(mBottomOuterStrokeColor, mCurrentBorderTintColor); - - final float[] positions = new float[6]; - positions[0] = 0f; - positions[1] = borderRatio; - positions[2] = 0.5f; - positions[3] = 0.5f; - positions[4] = 1f - borderRatio; - positions[5] = 1f; - - return new LinearGradient( - 0, rect.top, - 0, rect.bottom, - colors, positions, - Shader.TileMode.CLAMP); - } -} diff --git a/android/support/design/widget/CircularBorderDrawableLollipop.java b/android/support/design/widget/CircularBorderDrawableLollipop.java deleted file mode 100644 index 80084048..00000000 --- a/android/support/design/widget/CircularBorderDrawableLollipop.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.graphics.Outline; -import android.support.annotation.RequiresApi; - -/** - * Lollipop version of {@link CircularBorderDrawable}. - */ -@RequiresApi(21) -class CircularBorderDrawableLollipop extends CircularBorderDrawable { - - @Override - public void getOutline(Outline outline) { - copyBounds(mRect); - outline.setOval(mRect); - } - -} diff --git a/android/support/design/widget/CollapsingTextHelper.java b/android/support/design/widget/CollapsingTextHelper.java deleted file mode 100644 index a33cabc3..00000000 --- a/android/support/design/widget/CollapsingTextHelper.java +++ /dev/null @@ -1,723 +0,0 @@ -/* - * 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.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Typeface; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.v4.math.MathUtils; -import android.support.v4.text.TextDirectionHeuristicsCompat; -import android.support.v4.view.GravityCompat; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.TintTypedArray; -import android.text.TextPaint; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.View; -import android.view.animation.Interpolator; - -final class CollapsingTextHelper { - - // Pre-JB-MR2 doesn't support HW accelerated canvas scaled text so we will workaround it - // by using our own texture - private static final boolean USE_SCALING_TEXTURE = Build.VERSION.SDK_INT < 18; - - private static final boolean DEBUG_DRAW = false; - private static final Paint DEBUG_DRAW_PAINT; - static { - DEBUG_DRAW_PAINT = DEBUG_DRAW ? new Paint() : null; - if (DEBUG_DRAW_PAINT != null) { - DEBUG_DRAW_PAINT.setAntiAlias(true); - DEBUG_DRAW_PAINT.setColor(Color.MAGENTA); - } - } - - private final View mView; - - private boolean mDrawTitle; - private float mExpandedFraction; - - private final Rect mExpandedBounds; - private final Rect mCollapsedBounds; - private final RectF mCurrentBounds; - private int mExpandedTextGravity = Gravity.CENTER_VERTICAL; - private int mCollapsedTextGravity = Gravity.CENTER_VERTICAL; - private float mExpandedTextSize = 15; - private float mCollapsedTextSize = 15; - private ColorStateList mExpandedTextColor; - private ColorStateList mCollapsedTextColor; - - private float mExpandedDrawY; - private float mCollapsedDrawY; - private float mExpandedDrawX; - private float mCollapsedDrawX; - private float mCurrentDrawX; - private float mCurrentDrawY; - private Typeface mCollapsedTypeface; - private Typeface mExpandedTypeface; - private Typeface mCurrentTypeface; - - private CharSequence mText; - private CharSequence mTextToDraw; - private boolean mIsRtl; - - private boolean mUseTexture; - private Bitmap mExpandedTitleTexture; - private Paint mTexturePaint; - private float mTextureAscent; - private float mTextureDescent; - - private float mScale; - private float mCurrentTextSize; - - private int[] mState; - - private boolean mBoundsChanged; - - private final TextPaint mTextPaint; - - private Interpolator mPositionInterpolator; - private Interpolator mTextSizeInterpolator; - - private float mCollapsedShadowRadius, mCollapsedShadowDx, mCollapsedShadowDy; - private int mCollapsedShadowColor; - - private float mExpandedShadowRadius, mExpandedShadowDx, mExpandedShadowDy; - private int mExpandedShadowColor; - - public CollapsingTextHelper(View view) { - mView = view; - - mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG); - - mCollapsedBounds = new Rect(); - mExpandedBounds = new Rect(); - mCurrentBounds = new RectF(); - } - - void setTextSizeInterpolator(Interpolator interpolator) { - mTextSizeInterpolator = interpolator; - recalculate(); - } - - void setPositionInterpolator(Interpolator interpolator) { - mPositionInterpolator = interpolator; - recalculate(); - } - - void setExpandedTextSize(float textSize) { - if (mExpandedTextSize != textSize) { - mExpandedTextSize = textSize; - recalculate(); - } - } - - void setCollapsedTextSize(float textSize) { - if (mCollapsedTextSize != textSize) { - mCollapsedTextSize = textSize; - recalculate(); - } - } - - void setCollapsedTextColor(ColorStateList textColor) { - if (mCollapsedTextColor != textColor) { - mCollapsedTextColor = textColor; - recalculate(); - } - } - - void setExpandedTextColor(ColorStateList textColor) { - if (mExpandedTextColor != textColor) { - mExpandedTextColor = textColor; - recalculate(); - } - } - - void setExpandedBounds(int left, int top, int right, int bottom) { - if (!rectEquals(mExpandedBounds, left, top, right, bottom)) { - mExpandedBounds.set(left, top, right, bottom); - mBoundsChanged = true; - onBoundsChanged(); - } - } - - void setCollapsedBounds(int left, int top, int right, int bottom) { - if (!rectEquals(mCollapsedBounds, left, top, right, bottom)) { - mCollapsedBounds.set(left, top, right, bottom); - mBoundsChanged = true; - onBoundsChanged(); - } - } - - void onBoundsChanged() { - mDrawTitle = mCollapsedBounds.width() > 0 && mCollapsedBounds.height() > 0 - && mExpandedBounds.width() > 0 && mExpandedBounds.height() > 0; - } - - void setExpandedTextGravity(int gravity) { - if (mExpandedTextGravity != gravity) { - mExpandedTextGravity = gravity; - recalculate(); - } - } - - int getExpandedTextGravity() { - return mExpandedTextGravity; - } - - void setCollapsedTextGravity(int gravity) { - if (mCollapsedTextGravity != gravity) { - mCollapsedTextGravity = gravity; - recalculate(); - } - } - - int getCollapsedTextGravity() { - return mCollapsedTextGravity; - } - - void setCollapsedTextAppearance(int resId) { - TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), resId, - android.support.v7.appcompat.R.styleable.TextAppearance); - if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) { - mCollapsedTextColor = a.getColorStateList( - android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor); - } - if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) { - mCollapsedTextSize = a.getDimensionPixelSize( - android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, - (int) mCollapsedTextSize); - } - mCollapsedShadowColor = a.getInt( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0); - mCollapsedShadowDx = a.getFloat( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0); - mCollapsedShadowDy = a.getFloat( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0); - mCollapsedShadowRadius = a.getFloat( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0); - a.recycle(); - - if (Build.VERSION.SDK_INT >= 16) { - mCollapsedTypeface = readFontFamilyTypeface(resId); - } - - recalculate(); - } - - void setExpandedTextAppearance(int resId) { - TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), resId, - android.support.v7.appcompat.R.styleable.TextAppearance); - if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) { - mExpandedTextColor = a.getColorStateList( - android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor); - } - if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) { - mExpandedTextSize = a.getDimensionPixelSize( - android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, - (int) mExpandedTextSize); - } - mExpandedShadowColor = a.getInt( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0); - mExpandedShadowDx = a.getFloat( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0); - mExpandedShadowDy = a.getFloat( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0); - mExpandedShadowRadius = a.getFloat( - android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0); - a.recycle(); - - if (Build.VERSION.SDK_INT >= 16) { - mExpandedTypeface = readFontFamilyTypeface(resId); - } - - recalculate(); - } - - private Typeface readFontFamilyTypeface(int resId) { - final TypedArray a = mView.getContext().obtainStyledAttributes(resId, - new int[]{android.R.attr.fontFamily}); - try { - final String family = a.getString(0); - if (family != null) { - return Typeface.create(family, Typeface.NORMAL); - } - } finally { - a.recycle(); - } - return null; - } - - void setCollapsedTypeface(Typeface typeface) { - if (areTypefacesDifferent(mCollapsedTypeface, typeface)) { - mCollapsedTypeface = typeface; - recalculate(); - } - } - - void setExpandedTypeface(Typeface typeface) { - if (areTypefacesDifferent(mExpandedTypeface, typeface)) { - mExpandedTypeface = typeface; - recalculate(); - } - } - - void setTypefaces(Typeface typeface) { - mCollapsedTypeface = mExpandedTypeface = typeface; - recalculate(); - } - - Typeface getCollapsedTypeface() { - return mCollapsedTypeface != null ? mCollapsedTypeface : Typeface.DEFAULT; - } - - Typeface getExpandedTypeface() { - return mExpandedTypeface != null ? mExpandedTypeface : Typeface.DEFAULT; - } - - /** - * Set the value indicating the current scroll value. This decides how much of the - * background will be displayed, as well as the title metrics/positioning. - * - * A value of {@code 0.0} indicates that the layout is fully expanded. - * A value of {@code 1.0} indicates that the layout is fully collapsed. - */ - void setExpansionFraction(float fraction) { - fraction = MathUtils.clamp(fraction, 0f, 1f); - - if (fraction != mExpandedFraction) { - mExpandedFraction = fraction; - calculateCurrentOffsets(); - } - } - - final boolean setState(final int[] state) { - mState = state; - - if (isStateful()) { - recalculate(); - return true; - } - - return false; - } - - final boolean isStateful() { - return (mCollapsedTextColor != null && mCollapsedTextColor.isStateful()) - || (mExpandedTextColor != null && mExpandedTextColor.isStateful()); - } - - float getExpansionFraction() { - return mExpandedFraction; - } - - float getCollapsedTextSize() { - return mCollapsedTextSize; - } - - float getExpandedTextSize() { - return mExpandedTextSize; - } - - private void calculateCurrentOffsets() { - calculateOffsets(mExpandedFraction); - } - - private void calculateOffsets(final float fraction) { - interpolateBounds(fraction); - mCurrentDrawX = lerp(mExpandedDrawX, mCollapsedDrawX, fraction, - mPositionInterpolator); - mCurrentDrawY = lerp(mExpandedDrawY, mCollapsedDrawY, fraction, - mPositionInterpolator); - - setInterpolatedTextSize(lerp(mExpandedTextSize, mCollapsedTextSize, - fraction, mTextSizeInterpolator)); - - if (mCollapsedTextColor != mExpandedTextColor) { - // If the collapsed and expanded text colors are different, blend them based on the - // fraction - mTextPaint.setColor(blendColors( - getCurrentExpandedTextColor(), getCurrentCollapsedTextColor(), fraction)); - } else { - mTextPaint.setColor(getCurrentCollapsedTextColor()); - } - - mTextPaint.setShadowLayer( - lerp(mExpandedShadowRadius, mCollapsedShadowRadius, fraction, null), - lerp(mExpandedShadowDx, mCollapsedShadowDx, fraction, null), - lerp(mExpandedShadowDy, mCollapsedShadowDy, fraction, null), - blendColors(mExpandedShadowColor, mCollapsedShadowColor, fraction)); - - ViewCompat.postInvalidateOnAnimation(mView); - } - - @ColorInt - private int getCurrentExpandedTextColor() { - if (mState != null) { - return mExpandedTextColor.getColorForState(mState, 0); - } else { - return mExpandedTextColor.getDefaultColor(); - } - } - - @ColorInt - private int getCurrentCollapsedTextColor() { - if (mState != null) { - return mCollapsedTextColor.getColorForState(mState, 0); - } else { - return mCollapsedTextColor.getDefaultColor(); - } - } - - private void calculateBaseOffsets() { - final float currentTextSize = mCurrentTextSize; - - // We then calculate the collapsed text size, using the same logic - calculateUsingTextSize(mCollapsedTextSize); - float width = mTextToDraw != null ? - mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0; - final int collapsedAbsGravity = GravityCompat.getAbsoluteGravity(mCollapsedTextGravity, - mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR); - switch (collapsedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) { - case Gravity.BOTTOM: - mCollapsedDrawY = mCollapsedBounds.bottom; - break; - case Gravity.TOP: - mCollapsedDrawY = mCollapsedBounds.top - mTextPaint.ascent(); - break; - case Gravity.CENTER_VERTICAL: - default: - float textHeight = mTextPaint.descent() - mTextPaint.ascent(); - float textOffset = (textHeight / 2) - mTextPaint.descent(); - mCollapsedDrawY = mCollapsedBounds.centerY() + textOffset; - break; - } - switch (collapsedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) { - case Gravity.CENTER_HORIZONTAL: - mCollapsedDrawX = mCollapsedBounds.centerX() - (width / 2); - break; - case Gravity.RIGHT: - mCollapsedDrawX = mCollapsedBounds.right - width; - break; - case Gravity.LEFT: - default: - mCollapsedDrawX = mCollapsedBounds.left; - break; - } - - calculateUsingTextSize(mExpandedTextSize); - width = mTextToDraw != null - ? mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0; - final int expandedAbsGravity = GravityCompat.getAbsoluteGravity(mExpandedTextGravity, - mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR); - switch (expandedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) { - case Gravity.BOTTOM: - mExpandedDrawY = mExpandedBounds.bottom; - break; - case Gravity.TOP: - mExpandedDrawY = mExpandedBounds.top - mTextPaint.ascent(); - break; - case Gravity.CENTER_VERTICAL: - default: - float textHeight = mTextPaint.descent() - mTextPaint.ascent(); - float textOffset = (textHeight / 2) - mTextPaint.descent(); - mExpandedDrawY = mExpandedBounds.centerY() + textOffset; - break; - } - switch (expandedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) { - case Gravity.CENTER_HORIZONTAL: - mExpandedDrawX = mExpandedBounds.centerX() - (width / 2); - break; - case Gravity.RIGHT: - mExpandedDrawX = mExpandedBounds.right - width; - break; - case Gravity.LEFT: - default: - mExpandedDrawX = mExpandedBounds.left; - break; - } - - // The bounds have changed so we need to clear the texture - clearTexture(); - // Now reset the text size back to the original - setInterpolatedTextSize(currentTextSize); - } - - private void interpolateBounds(float fraction) { - mCurrentBounds.left = lerp(mExpandedBounds.left, mCollapsedBounds.left, - fraction, mPositionInterpolator); - mCurrentBounds.top = lerp(mExpandedDrawY, mCollapsedDrawY, - fraction, mPositionInterpolator); - mCurrentBounds.right = lerp(mExpandedBounds.right, mCollapsedBounds.right, - fraction, mPositionInterpolator); - mCurrentBounds.bottom = lerp(mExpandedBounds.bottom, mCollapsedBounds.bottom, - fraction, mPositionInterpolator); - } - - public void draw(Canvas canvas) { - final int saveCount = canvas.save(); - - if (mTextToDraw != null && mDrawTitle) { - float x = mCurrentDrawX; - float y = mCurrentDrawY; - - final boolean drawTexture = mUseTexture && mExpandedTitleTexture != null; - - final float ascent; - final float descent; - if (drawTexture) { - ascent = mTextureAscent * mScale; - descent = mTextureDescent * mScale; - } else { - ascent = mTextPaint.ascent() * mScale; - descent = mTextPaint.descent() * mScale; - } - - if (DEBUG_DRAW) { - // Just a debug tool, which drawn a magenta rect in the text bounds - canvas.drawRect(mCurrentBounds.left, y + ascent, mCurrentBounds.right, y + descent, - DEBUG_DRAW_PAINT); - } - - if (drawTexture) { - y += ascent; - } - - if (mScale != 1f) { - canvas.scale(mScale, mScale, x, y); - } - - if (drawTexture) { - // If we should use a texture, draw it instead of text - canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint); - } else { - canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y, mTextPaint); - } - } - - canvas.restoreToCount(saveCount); - } - - private boolean calculateIsRtl(CharSequence text) { - final boolean defaultIsRtl = ViewCompat.getLayoutDirection(mView) - == ViewCompat.LAYOUT_DIRECTION_RTL; - return (defaultIsRtl - ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL - : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR).isRtl(text, 0, text.length()); - } - - private void setInterpolatedTextSize(float textSize) { - calculateUsingTextSize(textSize); - - // Use our texture if the scale isn't 1.0 - mUseTexture = USE_SCALING_TEXTURE && mScale != 1f; - - if (mUseTexture) { - // Make sure we have an expanded texture if needed - ensureExpandedTexture(); - } - - ViewCompat.postInvalidateOnAnimation(mView); - } - - private boolean areTypefacesDifferent(Typeface first, Typeface second) { - return (first != null && !first.equals(second)) || (first == null && second != null); - } - - private void calculateUsingTextSize(final float textSize) { - if (mText == null) return; - - final float collapsedWidth = mCollapsedBounds.width(); - final float expandedWidth = mExpandedBounds.width(); - - final float availableWidth; - final float newTextSize; - boolean updateDrawText = false; - - if (isClose(textSize, mCollapsedTextSize)) { - newTextSize = mCollapsedTextSize; - mScale = 1f; - if (areTypefacesDifferent(mCurrentTypeface, mCollapsedTypeface)) { - mCurrentTypeface = mCollapsedTypeface; - updateDrawText = true; - } - availableWidth = collapsedWidth; - } else { - newTextSize = mExpandedTextSize; - if (areTypefacesDifferent(mCurrentTypeface, mExpandedTypeface)) { - mCurrentTypeface = mExpandedTypeface; - updateDrawText = true; - } - if (isClose(textSize, mExpandedTextSize)) { - // If we're close to the expanded text size, snap to it and use a scale of 1 - mScale = 1f; - } else { - // Else, we'll scale down from the expanded text size - mScale = textSize / mExpandedTextSize; - } - - final float textSizeRatio = mCollapsedTextSize / mExpandedTextSize; - // This is the size of the expanded bounds when it is scaled to match the - // collapsed text size - final float scaledDownWidth = expandedWidth * textSizeRatio; - - if (scaledDownWidth > collapsedWidth) { - // If the scaled down size is larger than the actual collapsed width, we need to - // cap the available width so that when the expanded text scales down, it matches - // the collapsed width - availableWidth = Math.min(collapsedWidth / textSizeRatio, expandedWidth); - } else { - // Otherwise we'll just use the expanded width - availableWidth = expandedWidth; - } - } - - if (availableWidth > 0) { - updateDrawText = (mCurrentTextSize != newTextSize) || mBoundsChanged || updateDrawText; - mCurrentTextSize = newTextSize; - mBoundsChanged = false; - } - - if (mTextToDraw == null || updateDrawText) { - mTextPaint.setTextSize(mCurrentTextSize); - mTextPaint.setTypeface(mCurrentTypeface); - // Use linear text scaling if we're scaling the canvas - mTextPaint.setLinearText(mScale != 1f); - - // If we don't currently have text to draw, or the text size has changed, ellipsize... - final CharSequence title = TextUtils.ellipsize(mText, mTextPaint, - availableWidth, TextUtils.TruncateAt.END); - if (!TextUtils.equals(title, mTextToDraw)) { - mTextToDraw = title; - mIsRtl = calculateIsRtl(mTextToDraw); - } - } - } - - private void ensureExpandedTexture() { - if (mExpandedTitleTexture != null || mExpandedBounds.isEmpty() - || TextUtils.isEmpty(mTextToDraw)) { - return; - } - - calculateOffsets(0f); - mTextureAscent = mTextPaint.ascent(); - mTextureDescent = mTextPaint.descent(); - - final int w = Math.round(mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length())); - final int h = Math.round(mTextureDescent - mTextureAscent); - - if (w <= 0 || h <= 0) { - return; // If the width or height are 0, return - } - - mExpandedTitleTexture = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - - Canvas c = new Canvas(mExpandedTitleTexture); - c.drawText(mTextToDraw, 0, mTextToDraw.length(), 0, h - mTextPaint.descent(), mTextPaint); - - if (mTexturePaint == null) { - // Make sure we have a paint - mTexturePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); - } - } - - public void recalculate() { - if (mView.getHeight() > 0 && mView.getWidth() > 0) { - // If we've already been laid out, calculate everything now otherwise we'll wait - // until a layout - calculateBaseOffsets(); - calculateCurrentOffsets(); - } - } - - /** - * Set the title to display - * - * @param text - */ - void setText(CharSequence text) { - if (text == null || !text.equals(mText)) { - mText = text; - mTextToDraw = null; - clearTexture(); - recalculate(); - } - } - - CharSequence getText() { - return mText; - } - - private void clearTexture() { - if (mExpandedTitleTexture != null) { - mExpandedTitleTexture.recycle(); - mExpandedTitleTexture = null; - } - } - - /** - * Returns true if {@code value} is 'close' to it's closest decimal value. Close is currently - * defined as it's difference being < 0.001. - */ - private static boolean isClose(float value, float targetValue) { - return Math.abs(value - targetValue) < 0.001f; - } - - ColorStateList getExpandedTextColor() { - return mExpandedTextColor; - } - - ColorStateList getCollapsedTextColor() { - return mCollapsedTextColor; - } - - /** - * Blend {@code color1} and {@code color2} using the given ratio. - * - * @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend, - * 1.0 will return {@code color2}. - */ - private static int blendColors(int color1, int color2, float ratio) { - final float inverseRatio = 1f - ratio; - float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio); - float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio); - float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio); - float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio); - return Color.argb((int) a, (int) r, (int) g, (int) b); - } - - private static float lerp(float startValue, float endValue, float fraction, - Interpolator interpolator) { - if (interpolator != null) { - fraction = interpolator.getInterpolation(fraction); - } - return AnimationUtils.lerp(startValue, endValue, fraction); - } - - private static boolean rectEquals(Rect r, int left, int top, int right, int bottom) { - return !(r.left != left || r.top != top || r.right != right || r.bottom != bottom); - } -} diff --git a/android/support/design/widget/CollapsingToolbarLayout.java b/android/support/design/widget/CollapsingToolbarLayout.java deleted file mode 100644 index 8c9b7d49..00000000 --- a/android/support/design/widget/CollapsingToolbarLayout.java +++ /dev/null @@ -1,1308 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.IntDef; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.support.annotation.RestrictTo; -import android.support.annotation.StyleRes; -import android.support.design.R; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.math.MathUtils; -import android.support.v4.util.ObjectsCompat; -import android.support.v4.view.GravityCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.WindowInsetsCompat; -import android.support.v4.widget.ViewGroupUtils; -import android.support.v7.widget.Toolbar; -import android.text.TextUtils; -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; - -/** - * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar. - * It is designed to be used as a direct child of a {@link AppBarLayout}. - * CollapsingToolbarLayout contains the following features: - * - * <h4>Collapsing title</h4> - * 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. - * - * <h4>Content scrim</h4> - * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold. - * You can change this via {@link #setContentScrim(Drawable)}. - * - * <h4>Status bar scrim</h4> - * A scrim which is show or hidden behind the status bar when the scroll position has hit a certain - * threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works - * on {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system - * windows. - * - * <h4>Parallax scrolling children</h4> - * 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)}. - * - * <h4>Pinned position children</h4> - * 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}. - * - * <p><strong>Do not manually add views to the Toolbar at run time</strong>. - * We will add a 'dummy view' to the Toolbar which allows us to work out the available space - * for the title. This can interfere with any views which you add.</p> - * - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_contentScrim - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_statusBarScrim - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_toolbarId - */ -public class CollapsingToolbarLayout extends FrameLayout { - - private static final int DEFAULT_SCRIM_ANIMATION_DURATION = 600; - - private boolean mRefreshToolbar = true; - private int mToolbarId; - private Toolbar mToolbar; - private View mToolbarDirectChild; - private View mDummyView; - - private int mExpandedMarginStart; - private int mExpandedMarginTop; - private int mExpandedMarginEnd; - private int mExpandedMarginBottom; - - private final Rect mTmpRect = new Rect(); - final CollapsingTextHelper mCollapsingTextHelper; - private boolean mCollapsingTitleEnabled; - private boolean mDrawCollapsingTitle; - - private Drawable mContentScrim; - Drawable mStatusBarScrim; - private int mScrimAlpha; - private boolean mScrimsAreShown; - private ValueAnimator mScrimAnimator; - private long mScrimAnimationDuration; - private int mScrimVisibleHeightTrigger = -1; - - private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener; - - int mCurrentOffset; - - WindowInsetsCompat mLastInsets; - - public CollapsingToolbarLayout(Context context) { - this(context, null); - } - - public CollapsingToolbarLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - ThemeUtils.checkAppCompatTheme(context); - - mCollapsingTextHelper = new CollapsingTextHelper(this); - mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); - - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.CollapsingToolbarLayout, defStyleAttr, - R.style.Widget_Design_CollapsingToolbar); - - mCollapsingTextHelper.setExpandedTextGravity( - a.getInt(R.styleable.CollapsingToolbarLayout_expandedTitleGravity, - GravityCompat.START | Gravity.BOTTOM)); - mCollapsingTextHelper.setCollapsedTextGravity( - a.getInt(R.styleable.CollapsingToolbarLayout_collapsedTitleGravity, - GravityCompat.START | Gravity.CENTER_VERTICAL)); - - mExpandedMarginStart = mExpandedMarginTop = mExpandedMarginEnd = mExpandedMarginBottom = - a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMargin, 0); - - if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) { - mExpandedMarginStart = a.getDimensionPixelSize( - R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart, 0); - } - if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) { - mExpandedMarginEnd = a.getDimensionPixelSize( - R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd, 0); - } - if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop)) { - mExpandedMarginTop = a.getDimensionPixelSize( - R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop, 0); - } - if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) { - mExpandedMarginBottom = a.getDimensionPixelSize( - R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0); - } - - mCollapsingTitleEnabled = a.getBoolean( - R.styleable.CollapsingToolbarLayout_titleEnabled, true); - setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title)); - - // First load the default text appearances - mCollapsingTextHelper.setExpandedTextAppearance( - R.style.TextAppearance_Design_CollapsingToolbar_Expanded); - mCollapsingTextHelper.setCollapsedTextAppearance( - android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Widget_ActionBar_Title); - - // Now overlay any custom text appearances - if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) { - mCollapsingTextHelper.setExpandedTextAppearance( - a.getResourceId( - R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 0)); - } - if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance)) { - mCollapsingTextHelper.setCollapsedTextAppearance( - a.getResourceId( - R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 0)); - } - - mScrimVisibleHeightTrigger = a.getDimensionPixelSize( - R.styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger, -1); - - mScrimAnimationDuration = a.getInt( - R.styleable.CollapsingToolbarLayout_scrimAnimationDuration, - DEFAULT_SCRIM_ANIMATION_DURATION); - - setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim)); - setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim)); - - mToolbarId = a.getResourceId(R.styleable.CollapsingToolbarLayout_toolbarId, -1); - - a.recycle(); - - setWillNotDraw(false); - - ViewCompat.setOnApplyWindowInsetsListener(this, - new android.support.v4.view.OnApplyWindowInsetsListener() { - @Override - public WindowInsetsCompat onApplyWindowInsets(View v, - WindowInsetsCompat insets) { - return onWindowInsetChanged(insets); - } - }); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - // Add an OnOffsetChangedListener if possible - final ViewParent parent = getParent(); - if (parent instanceof AppBarLayout) { - // Copy over from the ABL whether we should fit system windows - ViewCompat.setFitsSystemWindows(this, ViewCompat.getFitsSystemWindows((View) parent)); - - if (mOnOffsetChangedListener == null) { - mOnOffsetChangedListener = new OffsetUpdateListener(); - } - ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener); - - // We're attached, so lets request an inset dispatch - ViewCompat.requestApplyInsets(this); - } - } - - @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(); - } - - WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) { - WindowInsetsCompat newInsets = null; - - if (ViewCompat.getFitsSystemWindows(this)) { - // If we're set to fit system windows, keep the insets - newInsets = insets; - } - - // If our insets have changed, keep them and invalidate the scroll ranges... - if (!ObjectsCompat.equals(mLastInsets, newInsets)) { - mLastInsets = newInsets; - requestLayout(); - } - - // Consume the insets. This is done so that child views with fitSystemWindows=true do not - // get the default padding functionality from View - return insets.consumeSystemWindowInsets(); - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below. - // Instead, we draw it here, before our collapsing text. - ensureToolbar(); - if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) { - mContentScrim.mutate().setAlpha(mScrimAlpha); - mContentScrim.draw(canvas); - } - - // Let the collapsing text helper draw its text - if (mCollapsingTitleEnabled && mDrawCollapsingTitle) { - mCollapsingTextHelper.draw(canvas); - } - - // Now draw the status bar scrim - if (mStatusBarScrim != null && mScrimAlpha > 0) { - final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; - if (topInset > 0) { - mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(), - topInset - mCurrentOffset); - mStatusBarScrim.mutate().setAlpha(mScrimAlpha); - mStatusBarScrim.draw(canvas); - } - } - } - - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present), - // but in front of any other children which are behind it. To do this we intercept the - // drawChild() call, and draw our scrim just before the Toolbar is drawn - boolean invalidated = false; - if (mContentScrim != null && mScrimAlpha > 0 && isToolbarChild(child)) { - mContentScrim.mutate().setAlpha(mScrimAlpha); - mContentScrim.draw(canvas); - invalidated = true; - } - return super.drawChild(canvas, child, drawingTime) || invalidated; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (mContentScrim != null) { - mContentScrim.setBounds(0, 0, w, h); - } - } - - private void ensureToolbar() { - if (!mRefreshToolbar) { - return; - } - - // First clear out the current Toolbar - mToolbar = null; - mToolbarDirectChild = null; - - if (mToolbarId != -1) { - // If we have an ID set, try and find it and it's direct parent to us - mToolbar = findViewById(mToolbarId); - if (mToolbar != null) { - mToolbarDirectChild = findDirectChild(mToolbar); - } - } - - if (mToolbar == 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; - } - } - mToolbar = toolbar; - } - - updateDummyView(); - mRefreshToolbar = false; - } - - private boolean isToolbarChild(View child) { - return (mToolbarDirectChild == null || mToolbarDirectChild == this) - ? child == mToolbar - : child == mToolbarDirectChild; - } - - /** - * Returns the direct child of this layout, which itself is the ancestor of the - * given view. - */ - private View findDirectChild(final View descendant) { - View directChild = descendant; - for (ViewParent p = descendant.getParent(); p != this && p != null; p = p.getParent()) { - if (p instanceof View) { - directChild = (View) p; - } - } - return directChild; - } - - private void updateDummyView() { - if (!mCollapsingTitleEnabled && mDummyView != null) { - // If we have a dummy view and we have our title disabled, remove it from its parent - final ViewParent parent = mDummyView.getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).removeView(mDummyView); - } - } - if (mCollapsingTitleEnabled && mToolbar != null) { - if (mDummyView == null) { - mDummyView = new View(getContext()); - } - if (mDummyView.getParent() == null) { - mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - } - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - ensureToolbar(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - final int mode = MeasureSpec.getMode(heightMeasureSpec); - final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; - if (mode == MeasureSpec.UNSPECIFIED && topInset > 0) { - // If we have a top inset and we're set to wrap_content height we need to make sure - // we add the top inset to our height, therefore we re-measure - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - getMeasuredHeight() + topInset, MeasureSpec.EXACTLY); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - if (mLastInsets != null) { - // Shift down any views which are not set to fit system windows - final int insetTop = mLastInsets.getSystemWindowInsetTop(); - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - if (!ViewCompat.getFitsSystemWindows(child)) { - if (child.getTop() < insetTop) { - // If the child isn't set to fit system windows but is drawing within - // the inset offset it down - ViewCompat.offsetTopAndBottom(child, insetTop); - } - } - } - } - - // Update the collapsed bounds by getting it's transformed bounds - if (mCollapsingTitleEnabled && mDummyView != null) { - // We only draw the title if the dummy view is being displayed (Toolbar removes - // views if there is no space) - mDrawCollapsingTitle = ViewCompat.isAttachedToWindow(mDummyView) - && mDummyView.getVisibility() == VISIBLE; - - if (mDrawCollapsingTitle) { - final boolean isRtl = ViewCompat.getLayoutDirection(this) - == ViewCompat.LAYOUT_DIRECTION_RTL; - - // Update the collapsed bounds - final int maxOffset = getMaxOffsetForPinChild( - mToolbarDirectChild != null ? mToolbarDirectChild : mToolbar); - ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect); - mCollapsingTextHelper.setCollapsedBounds( - mTmpRect.left + (isRtl - ? mToolbar.getTitleMarginEnd() - : mToolbar.getTitleMarginStart()), - mTmpRect.top + maxOffset + mToolbar.getTitleMarginTop(), - mTmpRect.right + (isRtl - ? mToolbar.getTitleMarginStart() - : mToolbar.getTitleMarginEnd()), - mTmpRect.bottom + maxOffset - mToolbar.getTitleMarginBottom()); - - // Update the expanded bounds - mCollapsingTextHelper.setExpandedBounds( - isRtl ? mExpandedMarginEnd : mExpandedMarginStart, - mTmpRect.top + mExpandedMarginTop, - right - left - (isRtl ? mExpandedMarginStart : mExpandedMarginEnd), - bottom - top - mExpandedMarginBottom); - // Now recalculate using the new bounds - mCollapsingTextHelper.recalculate(); - } - } - - // Update our child view offset helpers. This needs to be done after the title has been - // setup, so that any Toolbars are in their original position - for (int i = 0, z = getChildCount(); i < z; i++) { - getViewOffsetHelper(getChildAt(i)).onViewLayout(); - } - - // Finally, set our minimum height to enable proper AppBarLayout collapsing - if (mToolbar != null) { - if (mCollapsingTitleEnabled && TextUtils.isEmpty(mCollapsingTextHelper.getText())) { - // If we do not currently have a title, try and grab it from the Toolbar - mCollapsingTextHelper.setText(mToolbar.getTitle()); - } - if (mToolbarDirectChild == null || mToolbarDirectChild == this) { - setMinimumHeight(getHeightWithMargins(mToolbar)); - } else { - setMinimumHeight(getHeightWithMargins(mToolbarDirectChild)); - } - } - - updateScrimVisibility(); - } - - private static int getHeightWithMargins(@NonNull final View view) { - final ViewGroup.LayoutParams lp = view.getLayoutParams(); - if (lp instanceof MarginLayoutParams) { - final MarginLayoutParams mlp = (MarginLayoutParams) lp; - return view.getHeight() + mlp.topMargin + mlp.bottomMargin; - } - return view.getHeight(); - } - - static ViewOffsetHelper getViewOffsetHelper(View view) { - ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper); - if (offsetHelper == null) { - offsetHelper = new ViewOffsetHelper(view); - view.setTag(R.id.view_offset_helper, offsetHelper); - } - return offsetHelper; - } - - /** - * 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; - updateDummyView(); - 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; - } - - /** - * Set whether the content scrim and/or status bar scrim should be shown or not. Any change - * in the vertical scroll may overwrite this value. Any visibility change will be animated if - * this view has already been laid out. - * - * @param shown whether the scrims should be shown - * - * @see #getStatusBarScrim() - * @see #getContentScrim() - */ - public void setScrimsShown(boolean shown) { - setScrimsShown(shown, ViewCompat.isLaidOut(this) && !isInEditMode()); - } - - /** - * Set whether the content scrim and/or status bar scrim should be shown or not. Any change - * in the vertical scroll may overwrite this value. - * - * @param shown whether the scrims should be shown - * @param animate whether to animate the visibility change - * - * @see #getStatusBarScrim() - * @see #getContentScrim() - */ - public void setScrimsShown(boolean shown, boolean animate) { - if (mScrimsAreShown != shown) { - if (animate) { - animateScrim(shown ? 0xFF : 0x0); - } else { - setScrimAlpha(shown ? 0xFF : 0x0); - } - mScrimsAreShown = shown; - } - } - - private void animateScrim(int targetAlpha) { - ensureToolbar(); - if (mScrimAnimator == null) { - mScrimAnimator = new ValueAnimator(); - mScrimAnimator.setDuration(mScrimAnimationDuration); - mScrimAnimator.setInterpolator( - targetAlpha > mScrimAlpha - ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR - : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR); - mScrimAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - setScrimAlpha((int) animator.getAnimatedValue()); - } - }); - } else if (mScrimAnimator.isRunning()) { - mScrimAnimator.cancel(); - } - - mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha); - mScrimAnimator.start(); - } - - void setScrimAlpha(int alpha) { - if (alpha != mScrimAlpha) { - final Drawable contentScrim = mContentScrim; - if (contentScrim != null && mToolbar != null) { - ViewCompat.postInvalidateOnAnimation(mToolbar); - } - mScrimAlpha = alpha; - ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); - } - } - - int getScrimAlpha() { - return mScrimAlpha; - } - - /** - * Set the drawable to use for the content scrim from resources. Providing null will disable - * the scrim functionality. - * - * @param drawable the drawable to display - * - * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim - * @see #getContentScrim() - */ - public void setContentScrim(@Nullable Drawable drawable) { - if (mContentScrim != drawable) { - if (mContentScrim != null) { - mContentScrim.setCallback(null); - } - mContentScrim = drawable != null ? drawable.mutate() : null; - if (mContentScrim != null) { - mContentScrim.setBounds(0, 0, getWidth(), getHeight()); - mContentScrim.setCallback(this); - mContentScrim.setAlpha(mScrimAlpha); - } - ViewCompat.postInvalidateOnAnimation(this); - } - } - - /** - * Set the color to use for the content scrim. - * - * @param color the color to display - * - * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim - * @see #getContentScrim() - */ - public void setContentScrimColor(@ColorInt int color) { - setContentScrim(new ColorDrawable(color)); - } - - /** - * Set the drawable to use for the content scrim from resources. - * - * @param resId drawable resource id - * - * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim - * @see #getContentScrim() - */ - public void setContentScrimResource(@DrawableRes int resId) { - setContentScrim(ContextCompat.getDrawable(getContext(), resId)); - - } - - /** - * Returns the drawable which is used for the foreground scrim. - * - * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim - * @see #setContentScrim(Drawable) - */ - @Nullable - public Drawable getContentScrim() { - return mContentScrim; - } - - /** - * Set the drawable to use for the status bar scrim from resources. - * Providing null will disable the scrim functionality. - * - * <p>This scrim is only shown when we have been given a top system inset.</p> - * - * @param drawable the drawable to display - * - * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim - * @see #getStatusBarScrim() - */ - public void setStatusBarScrim(@Nullable Drawable drawable) { - if (mStatusBarScrim != drawable) { - if (mStatusBarScrim != null) { - mStatusBarScrim.setCallback(null); - } - mStatusBarScrim = drawable != null ? drawable.mutate() : null; - if (mStatusBarScrim != null) { - if (mStatusBarScrim.isStateful()) { - mStatusBarScrim.setState(getDrawableState()); - } - DrawableCompat.setLayoutDirection(mStatusBarScrim, - ViewCompat.getLayoutDirection(this)); - mStatusBarScrim.setVisible(getVisibility() == VISIBLE, false); - mStatusBarScrim.setCallback(this); - mStatusBarScrim.setAlpha(mScrimAlpha); - } - ViewCompat.postInvalidateOnAnimation(this); - } - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - final int[] state = getDrawableState(); - boolean changed = false; - - Drawable d = mStatusBarScrim; - if (d != null && d.isStateful()) { - changed |= d.setState(state); - } - d = mContentScrim; - if (d != null && d.isStateful()) { - changed |= d.setState(state); - } - if (mCollapsingTextHelper != null) { - changed |= mCollapsingTextHelper.setState(state); - } - - if (changed) { - invalidate(); - } - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || who == mContentScrim || who == mStatusBarScrim; - } - - @Override - public void setVisibility(int visibility) { - super.setVisibility(visibility); - - final boolean visible = visibility == VISIBLE; - if (mStatusBarScrim != null && mStatusBarScrim.isVisible() != visible) { - mStatusBarScrim.setVisible(visible, false); - } - if (mContentScrim != null && mContentScrim.isVisible() != visible) { - mContentScrim.setVisible(visible, false); - } - } - - /** - * Set the color to use for the status bar scrim. - * - * <p>This scrim is only shown when we have been given a top system inset.</p> - * - * @param color the color to display - * - * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim - * @see #getStatusBarScrim() - */ - public void setStatusBarScrimColor(@ColorInt int color) { - setStatusBarScrim(new ColorDrawable(color)); - } - - /** - * Set the drawable to use for the content scrim from resources. - * - * @param resId drawable resource id - * - * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim - * @see #getStatusBarScrim() - */ - public void setStatusBarScrimResource(@DrawableRes int resId) { - setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId)); - } - - /** - * Returns the drawable which is used for the status bar scrim. - * - * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim - * @see #setStatusBarScrim(Drawable) - */ - @Nullable - public Drawable getStatusBarScrim() { - return mStatusBarScrim; - } - - /** - * Sets the text color and size for the collapsed title from the specified - * TextAppearance resource. - * - * @attr ref android.support.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) { - setCollapsedTitleTextColor(ColorStateList.valueOf(color)); - } - - /** - * Sets the text colors of the collapsed title. - * - * @param colors ColorStateList containing the new text colors - */ - public void setCollapsedTitleTextColor(@NonNull ColorStateList colors) { - mCollapsingTextHelper.setCollapsedTextColor(colors); - } - - /** - * 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 android.support.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 android.support.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 android.support.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) { - setExpandedTitleTextColor(ColorStateList.valueOf(color)); - } - - /** - * Sets the text colors of the expanded title. - * - * @param colors ColorStateList containing the new text colors - */ - public void setExpandedTitleTextColor(@NonNull ColorStateList colors) { - mCollapsingTextHelper.setExpandedTextColor(colors); - } - - /** - * 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 android.support.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 android.support.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 android.support.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 android.support.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 android.support.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 android.support.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 android.support.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 android.support.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 android.support.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 android.support.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 android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom - */ - public void setExpandedTitleMarginBottom(int margin) { - mExpandedMarginBottom = margin; - requestLayout(); - } - - /** - * Set the amount of visible height in pixels used to define when to trigger a scrim - * visibility change. - * - * <p>If the visible height of this view is less than the given value, the scrims will be - * made visible, otherwise they are hidden.</p> - * - * @param height value in pixels used to define when to trigger a scrim visibility change - * - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_scrimVisibleHeightTrigger - */ - public void setScrimVisibleHeightTrigger(@IntRange(from = 0) final int height) { - if (mScrimVisibleHeightTrigger != height) { - mScrimVisibleHeightTrigger = height; - // Update the scrim visibility - updateScrimVisibility(); - } - } - - /** - * Returns the amount of visible height in pixels used to define when to trigger a scrim - * visibility change. - * - * @see #setScrimVisibleHeightTrigger(int) - */ - public int getScrimVisibleHeightTrigger() { - if (mScrimVisibleHeightTrigger >= 0) { - // If we have one explicitly set, return it - return mScrimVisibleHeightTrigger; - } - - // Otherwise we'll use the default computed value - final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; - - final int minHeight = ViewCompat.getMinimumHeight(this); - if (minHeight > 0) { - // If we have a minHeight set, lets use 2 * minHeight (capped at our height) - return Math.min((minHeight * 2) + insetTop, getHeight()); - } - - // If we reach here then we don't have a min height set. Instead we'll take a - // guess at 1/3 of our height being visible - return getHeight() / 3; - } - - /** - * Set the duration used for scrim visibility animations. - * - * @param duration the duration to use in milliseconds - * - * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_scrimAnimationDuration - */ - public void setScrimAnimationDuration(@IntRange(from = 0) final long duration) { - mScrimAnimationDuration = duration; - } - - /** - * Returns the duration in milliseconds used for scrim visibility animations. - */ - public long getScrimAnimationDuration() { - return mScrimAnimationDuration; - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams; - } - - @Override - protected LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - } - - @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 */ - @RestrictTo(LIBRARY_GROUP) - @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 CollapsingToolbarLayout}. - */ - 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.CollapsingToolbarLayout_Layout); - mCollapseMode = a.getInt( - R.styleable.CollapsingToolbarLayout_Layout_layout_collapseMode, - COLLAPSE_MODE_OFF); - setParallaxMultiplier(a.getFloat( - R.styleable.CollapsingToolbarLayout_Layout_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); - } - - @RequiresApi(19) - public LayoutParams(FrameLayout.LayoutParams source) { - // The copy constructor called here only exists on API 19+. - 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; - } - } - - /** - * Show or hide the scrims if needed - */ - final void updateScrimVisibility() { - if (mContentScrim != null || mStatusBarScrim != null) { - setScrimsShown(getHeight() + mCurrentOffset < getScrimVisibleHeightTrigger()); - } - } - - final int getMaxOffsetForPinChild(View child) { - final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - return getHeight() - - offsetHelper.getLayoutTop() - - child.getHeight() - - lp.bottomMargin; - } - - private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener { - OffsetUpdateListener() { - } - - @Override - public void onOffsetChanged(AppBarLayout layout, int verticalOffset) { - mCurrentOffset = verticalOffset; - - final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 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: - offsetHelper.setTopAndBottomOffset(MathUtils.clamp( - -verticalOffset, 0, getMaxOffsetForPinChild(child))); - break; - case LayoutParams.COLLAPSE_MODE_PARALLAX: - offsetHelper.setTopAndBottomOffset( - Math.round(-verticalOffset * lp.mParallaxMult)); - break; - } - } - - // Show or hide the scrims if needed - updateScrimVisibility(); - - if (mStatusBarScrim != null && insetTop > 0) { - ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); - } - - // Update the collapsing text's fraction - final int expandRange = getHeight() - ViewCompat.getMinimumHeight( - CollapsingToolbarLayout.this) - insetTop; - mCollapsingTextHelper.setExpansionFraction( - Math.abs(verticalOffset) / (float) expandRange); - } - } -} diff --git a/android/support/design/widget/CoordinatorLayout.java b/android/support/design/widget/CoordinatorLayout.java index 03cce024..b7f47f40 100644 --- a/android/support/design/widget/CoordinatorLayout.java +++ b/android/support/design/widget/CoordinatorLayout.java @@ -366,7 +366,11 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen return insets; } - final WindowInsetsCompat getLastWindowInsets() { + /** + * @hide + */ + @RestrictTo(LIBRARY_GROUP) + public final WindowInsetsCompat getLastWindowInsets() { return mLastInsets; } diff --git a/android/support/design/widget/DrawableUtils.java b/android/support/design/widget/DrawableUtils.java deleted file mode 100644 index df1c04b0..00000000 --- a/android/support/design/widget/DrawableUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.graphics.drawable.Drawable; -import android.graphics.drawable.DrawableContainer; -import android.util.Log; - -import java.lang.reflect.Method; - -/** - * Caution. Gross hacks ahead. - */ -class DrawableUtils { - - private static final String LOG_TAG = "DrawableUtils"; - - private static Method sSetConstantStateMethod; - private static boolean sSetConstantStateMethodFetched; - - private DrawableUtils() {} - - static boolean setContainerConstantState(DrawableContainer drawable, - Drawable.ConstantState constantState) { - // We can use getDeclaredMethod() on v9+ - return setContainerConstantStateV9(drawable, constantState); - } - - private static boolean setContainerConstantStateV9(DrawableContainer drawable, - Drawable.ConstantState constantState) { - if (!sSetConstantStateMethodFetched) { - try { - sSetConstantStateMethod = DrawableContainer.class.getDeclaredMethod( - "setConstantState", DrawableContainer.DrawableContainerState.class); - sSetConstantStateMethod.setAccessible(true); - } catch (NoSuchMethodException e) { - Log.e(LOG_TAG, "Could not fetch setConstantState(). Oh well."); - } - sSetConstantStateMethodFetched = true; - } - if (sSetConstantStateMethod != null) { - try { - sSetConstantStateMethod.invoke(drawable, constantState); - return true; - } catch (Exception e) { - Log.e(LOG_TAG, "Could not invoke setConstantState(). Oh well."); - } - } - return false; - } -} diff --git a/android/support/design/widget/FloatingActionButton.java b/android/support/design/widget/FloatingActionButton.java deleted file mode 100644 index f37b3798..00000000 --- a/android/support/design/widget/FloatingActionButton.java +++ /dev/null @@ -1,870 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.VisibleForTesting; -import android.support.design.R; -import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ViewGroupUtils; -import android.support.v7.widget.AppCompatImageHelper; -import android.util.AttributeSet; -import android.util.Log; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -/** - * Floating action buttons are used for a special type of promoted action. They are distinguished - * by a circled icon floating above the UI and have special motion behaviors related to morphing, - * launching, and the transferring anchor point. - * - * <p>Floating action buttons come in two sizes: the default and the mini. The size can be - * controlled with the {@code fabSize} attribute.</p> - * - * <p>As this class descends from {@link ImageView}, you can control the icon which is displayed - * via {@link #setImageDrawable(Drawable)}.</p> - * - * <p>The background color of this view defaults to the your theme's {@code colorAccent}. If you - * wish to change this at runtime then you can do so via - * {@link #setBackgroundTintList(ColorStateList)}.</p> - */ -@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class) -public class FloatingActionButton extends VisibilityAwareImageButton { - - private static final String LOG_TAG = "FloatingActionButton"; - - /** - * Callback to be invoked when the visibility of a FloatingActionButton changes. - */ - public abstract static class OnVisibilityChangedListener { - /** - * Called when a FloatingActionButton has been - * {@link #show(OnVisibilityChangedListener) shown}. - * - * @param fab the FloatingActionButton that was shown. - */ - public void onShown(FloatingActionButton fab) {} - - /** - * Called when a FloatingActionButton has been - * {@link #hide(OnVisibilityChangedListener) hidden}. - * - * @param fab the FloatingActionButton that was hidden. - */ - public void onHidden(FloatingActionButton fab) {} - } - - // These values must match those in the attrs declaration - - /** - * The mini sized button. Will always been smaller than {@link #SIZE_NORMAL}. - * - * @see #setSize(int) - */ - public static final int SIZE_MINI = 1; - - /** - * The normal sized button. Will always been larger than {@link #SIZE_MINI}. - * - * @see #setSize(int) - */ - public static final int SIZE_NORMAL = 0; - - /** - * Size which will change based on the window size. For small sized windows - * (largest screen dimension < 470dp) this will select a small sized button, and for - * larger sized windows it will select a larger size. - * - * @see #setSize(int) - */ - public static final int SIZE_AUTO = -1; - - /** - * Indicates that FloatingActionButton should not have a custom size. - */ - public static final int NO_CUSTOM_SIZE = 0; - - /** - * The switch point for the largest screen edge where SIZE_AUTO switches from mini to normal. - */ - private static final int AUTO_MINI_LARGEST_SCREEN_WIDTH = 470; - - /** @hide */ - @RestrictTo(LIBRARY_GROUP) - @Retention(RetentionPolicy.SOURCE) - @IntDef({SIZE_MINI, SIZE_NORMAL, SIZE_AUTO}) - public @interface Size {} - - private ColorStateList mBackgroundTint; - private PorterDuff.Mode mBackgroundTintMode; - - private int mBorderWidth; - private int mRippleColor; - private int mSize; - private int mCustomSize; - int mImagePadding; - private int mMaxImageSize; - - boolean mCompatPadding; - final Rect mShadowPadding = new Rect(); - private final Rect mTouchArea = new Rect(); - - private AppCompatImageHelper mImageHelper; - - private FloatingActionButtonImpl mImpl; - - public FloatingActionButton(Context context) { - this(context, null); - } - - public FloatingActionButton(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - ThemeUtils.checkAppCompatTheme(context); - - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.FloatingActionButton, defStyleAttr, - R.style.Widget_Design_FloatingActionButton); - mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint); - mBackgroundTintMode = ViewUtils.parseTintMode(a.getInt( - R.styleable.FloatingActionButton_backgroundTintMode, -1), null); - mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0); - mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO); - mCustomSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fabCustomSize, - 0); - mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0); - final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f); - final float pressedTranslationZ = a.getDimension( - R.styleable.FloatingActionButton_pressedTranslationZ, 0f); - mCompatPadding = a.getBoolean(R.styleable.FloatingActionButton_useCompatPadding, false); - a.recycle(); - - mImageHelper = new AppCompatImageHelper(this); - mImageHelper.loadFromAttributes(attrs, defStyleAttr); - - mMaxImageSize = (int) getResources().getDimension(R.dimen.design_fab_image_size); - - getImpl().setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode, - mRippleColor, mBorderWidth); - getImpl().setElevation(elevation); - getImpl().setPressedTranslationZ(pressedTranslationZ); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int preferredSize = getSizeDimension(); - - mImagePadding = (preferredSize - mMaxImageSize) / 2; - getImpl().updatePadding(); - - final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec); - final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec); - - // As we want to stay circular, we set both dimensions to be the - // smallest resolved dimension - final int d = Math.min(w, h); - - // We add the shadow's padding to the measured dimension - setMeasuredDimension( - d + mShadowPadding.left + mShadowPadding.right, - d + mShadowPadding.top + mShadowPadding.bottom); - } - - /** - * Returns the ripple color for this button. - * - * @return the ARGB color used for the ripple - * @see #setRippleColor(int) - */ - @ColorInt - public int getRippleColor() { - return mRippleColor; - } - - /** - * Sets the ripple color for this button. - * - * <p>When running on devices with KitKat or below, we draw this color as a filled circle - * rather than a ripple.</p> - * - * @param color ARGB color to use for the ripple - * @attr ref android.support.design.R.styleable#FloatingActionButton_rippleColor - * @see #getRippleColor() - */ - public void setRippleColor(@ColorInt int color) { - if (mRippleColor != color) { - mRippleColor = color; - getImpl().setRippleColor(color); - } - } - - /** - * Returns the tint applied to the background drawable, if specified. - * - * @return the tint applied to the background drawable - * @see #setBackgroundTintList(ColorStateList) - */ - @Nullable - @Override - public ColorStateList getBackgroundTintList() { - return mBackgroundTint; - } - - /** - * Applies a tint to the background drawable. Does not modify the current tint - * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. - * - * @param tint the tint to apply, may be {@code null} to clear tint - */ - @Override - public void setBackgroundTintList(@Nullable ColorStateList tint) { - if (mBackgroundTint != tint) { - mBackgroundTint = tint; - getImpl().setBackgroundTintList(tint); - } - } - - /** - * Returns the blending mode used to apply the tint to the background - * drawable, if specified. - * - * @return the blending mode used to apply the tint to the background - * drawable - * @see #setBackgroundTintMode(PorterDuff.Mode) - */ - @Nullable - @Override - public PorterDuff.Mode getBackgroundTintMode() { - return mBackgroundTintMode; - } - - /** - * Specifies the blending mode used to apply the tint specified by - * {@link #setBackgroundTintList(ColorStateList)}} to the background - * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. - * - * @param tintMode the blending mode used to apply the tint, may be - * {@code null} to clear tint - */ - @Override - public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { - if (mBackgroundTintMode != tintMode) { - mBackgroundTintMode = tintMode; - getImpl().setBackgroundTintMode(tintMode); - } - } - - @Override - public void setBackgroundDrawable(Drawable background) { - Log.i(LOG_TAG, "Setting a custom background is not supported."); - } - - @Override - public void setBackgroundResource(int resid) { - Log.i(LOG_TAG, "Setting a custom background is not supported."); - } - - @Override - public void setBackgroundColor(int color) { - Log.i(LOG_TAG, "Setting a custom background is not supported."); - } - - @Override - public void setImageResource(@DrawableRes int resId) { - // Intercept this call and instead retrieve the Drawable via the image helper - mImageHelper.setImageResource(resId); - } - - /** - * Shows the button. - * <p>This method will animate the button show if the view has already been laid out.</p> - */ - public void show() { - show(null); - } - - /** - * Shows the button. - * <p>This method will animate the button show if the view has already been laid out.</p> - * - * @param listener the listener to notify when this view is shown - */ - public void show(@Nullable final OnVisibilityChangedListener listener) { - show(listener, true); - } - - void show(OnVisibilityChangedListener listener, boolean fromUser) { - getImpl().show(wrapOnVisibilityChangedListener(listener), fromUser); - } - - /** - * Hides the button. - * <p>This method will animate the button hide if the view has already been laid out.</p> - */ - public void hide() { - hide(null); - } - - /** - * Hides the button. - * <p>This method will animate the button hide if the view has already been laid out.</p> - * - * @param listener the listener to notify when this view is hidden - */ - public void hide(@Nullable OnVisibilityChangedListener listener) { - hide(listener, true); - } - - void hide(@Nullable OnVisibilityChangedListener listener, boolean fromUser) { - getImpl().hide(wrapOnVisibilityChangedListener(listener), fromUser); - } - - /** - * Set whether FloatingActionButton should add inner padding on platforms Lollipop and after, - * to ensure consistent dimensions on all platforms. - * - * @param useCompatPadding true if FloatingActionButton is adding inner padding on platforms - * Lollipop and after, to ensure consistent dimensions on all platforms. - * - * @attr ref android.support.design.R.styleable#FloatingActionButton_useCompatPadding - * @see #getUseCompatPadding() - */ - public void setUseCompatPadding(boolean useCompatPadding) { - if (mCompatPadding != useCompatPadding) { - mCompatPadding = useCompatPadding; - getImpl().onCompatShadowChanged(); - } - } - - /** - * Returns whether FloatingActionButton will add inner padding on platforms Lollipop and after. - * - * @return true if FloatingActionButton is adding inner padding on platforms Lollipop and after, - * to ensure consistent dimensions on all platforms. - * - * @attr ref android.support.design.R.styleable#FloatingActionButton_useCompatPadding - * @see #setUseCompatPadding(boolean) - */ - public boolean getUseCompatPadding() { - return mCompatPadding; - } - - /** - * Sets the size of the button. - * - * <p>The options relate to the options available on the material design specification. - * {@link #SIZE_NORMAL} is larger than {@link #SIZE_MINI}. {@link #SIZE_AUTO} will choose - * an appropriate size based on the screen size.</p> - * - * @param size one of {@link #SIZE_NORMAL}, {@link #SIZE_MINI} or {@link #SIZE_AUTO} - * - * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize - */ - public void setSize(@Size int size) { - if (size != mSize) { - mSize = size; - requestLayout(); - } - } - - /** - * Returns the chosen size for this button. - * - * @return one of {@link #SIZE_NORMAL}, {@link #SIZE_MINI} or {@link #SIZE_AUTO} - * @see #setSize(int) - */ - @Size - public int getSize() { - return mSize; - } - - @Nullable - private InternalVisibilityChangedListener wrapOnVisibilityChangedListener( - @Nullable final OnVisibilityChangedListener listener) { - if (listener == null) { - return null; - } - - return new InternalVisibilityChangedListener() { - @Override - public void onShown() { - listener.onShown(FloatingActionButton.this); - } - - @Override - public void onHidden() { - listener.onHidden(FloatingActionButton.this); - } - }; - } - - /** - * Sets the size of the button to be a custom value in pixels. If set to - * {@link #NO_CUSTOM_SIZE}, custom size will not be used and size will be calculated according - * to {@link #setSize(int)} method. - * - * @param size preferred size in pixels, or zero - * - * @attr ref android.support.design.R.styleable#FloatingActionButton_fabCustomSize - */ - public void setCustomSize(int size) { - if (size < 0) { - throw new IllegalArgumentException("Custom size should be non-negative."); - } - mCustomSize = size; - } - - /** - * Returns the custom size for this button. - * - * @return size in pixels, or {@link #NO_CUSTOM_SIZE} - */ - public int getCustomSize() { - return mCustomSize; - } - - int getSizeDimension() { - return getSizeDimension(mSize); - } - - private int getSizeDimension(@Size final int size) { - final Resources res = getResources(); - // If custom size is set, return it - if (mCustomSize != NO_CUSTOM_SIZE) { - return mCustomSize; - } - switch (size) { - case SIZE_AUTO: - // If we're set to auto, grab the size from resources and refresh - final int width = res.getConfiguration().screenWidthDp; - final int height = res.getConfiguration().screenHeightDp; - return Math.max(width, height) < AUTO_MINI_LARGEST_SCREEN_WIDTH - ? getSizeDimension(SIZE_MINI) - : getSizeDimension(SIZE_NORMAL); - case SIZE_MINI: - return res.getDimensionPixelSize(R.dimen.design_fab_size_mini); - case SIZE_NORMAL: - default: - return res.getDimensionPixelSize(R.dimen.design_fab_size_normal); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - getImpl().onAttachedToWindow(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getImpl().onDetachedFromWindow(); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - getImpl().onDrawableStateChanged(getDrawableState()); - } - - @Override - public void jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState(); - getImpl().jumpDrawableToCurrentState(); - } - - /** - * Return in {@code rect} the bounds of the actual floating action button content in view-local - * coordinates. This is defined as anything within any visible shadow. - * - * @return true if this view actually has been laid out and has a content rect, else false. - */ - public boolean getContentRect(@NonNull Rect rect) { - if (ViewCompat.isLaidOut(this)) { - rect.set(0, 0, getWidth(), getHeight()); - rect.left += mShadowPadding.left; - rect.top += mShadowPadding.top; - rect.right -= mShadowPadding.right; - rect.bottom -= mShadowPadding.bottom; - return true; - } else { - return false; - } - } - - /** - * Returns the FloatingActionButton's background, minus any compatible shadow implementation. - */ - @NonNull - public Drawable getContentBackground() { - return getImpl().getContentBackground(); - } - - private static int resolveAdjustedSize(int desiredSize, int measureSpec) { - int result = desiredSize; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - switch (specMode) { - case MeasureSpec.UNSPECIFIED: - // Parent says we can be as big as we want. Just don't be larger - // than max size imposed on ourselves. - result = desiredSize; - break; - case MeasureSpec.AT_MOST: - // Parent says we can be as big as we want, up to specSize. - // Don't be larger than specSize, and don't be larger than - // the max size imposed on ourselves. - result = Math.min(desiredSize, specSize); - break; - case MeasureSpec.EXACTLY: - // No choice. Do what we are told. - result = specSize; - break; - } - return result; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - // Skipping the gesture if it doesn't start in in the FAB 'content' area - if (getContentRect(mTouchArea) - && !mTouchArea.contains((int) ev.getX(), (int) ev.getY())) { - return false; - } - break; - } - return super.onTouchEvent(ev); - } - - /** - * Behavior designed for use with {@link FloatingActionButton} instances. Its main function - * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do - * not cover them. - */ - public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> { - private static final boolean AUTO_HIDE_DEFAULT = true; - - private Rect mTmpRect; - private OnVisibilityChangedListener mInternalAutoHideListener; - private boolean mAutoHideEnabled; - - public Behavior() { - super(); - mAutoHideEnabled = AUTO_HIDE_DEFAULT; - } - - public Behavior(Context context, AttributeSet attrs) { - super(context, attrs); - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.FloatingActionButton_Behavior_Layout); - mAutoHideEnabled = a.getBoolean( - R.styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide, - AUTO_HIDE_DEFAULT); - a.recycle(); - } - - /** - * Sets whether the associated FloatingActionButton automatically hides when there is - * not enough space to be displayed. This works with {@link AppBarLayout} - * and {@link BottomSheetBehavior}. - * - * @attr ref android.support.design.R.styleable#FloatingActionButton_Behavior_Layout_behavior_autoHide - * @param autoHide true to enable automatic hiding - */ - public void setAutoHideEnabled(boolean autoHide) { - mAutoHideEnabled = autoHide; - } - - /** - * Returns whether the associated FloatingActionButton automatically hides when there is - * not enough space to be displayed. - * - * @attr ref android.support.design.R.styleable#FloatingActionButton_Behavior_Layout_behavior_autoHide - * @return true if enabled - */ - public boolean isAutoHideEnabled() { - return mAutoHideEnabled; - } - - @Override - public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams lp) { - if (lp.dodgeInsetEdges == Gravity.NO_GRAVITY) { - // If the developer hasn't set dodgeInsetEdges, lets set it to BOTTOM so that - // we dodge any Snackbars - lp.dodgeInsetEdges = Gravity.BOTTOM; - } - } - - @Override - public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, - View dependency) { - if (dependency instanceof AppBarLayout) { - // If we're depending on an AppBarLayout we will show/hide it automatically - // if the FAB is anchored to the AppBarLayout - updateFabVisibilityForAppBarLayout(parent, (AppBarLayout) dependency, child); - } else if (isBottomSheet(dependency)) { - updateFabVisibilityForBottomSheet(dependency, child); - } - return false; - } - - private static boolean isBottomSheet(@NonNull View view) { - final ViewGroup.LayoutParams lp = view.getLayoutParams(); - if (lp instanceof CoordinatorLayout.LayoutParams) { - return ((CoordinatorLayout.LayoutParams) lp) - .getBehavior() instanceof BottomSheetBehavior; - } - return false; - } - - @VisibleForTesting - void setInternalAutoHideListener(OnVisibilityChangedListener listener) { - mInternalAutoHideListener = listener; - } - - private boolean shouldUpdateVisibility(View dependency, FloatingActionButton child) { - final CoordinatorLayout.LayoutParams lp = - (CoordinatorLayout.LayoutParams) child.getLayoutParams(); - if (!mAutoHideEnabled) { - return false; - } - - if (lp.getAnchorId() != dependency.getId()) { - // The anchor ID doesn't match the dependency, so we won't automatically - // show/hide the FAB - return false; - } - - //noinspection RedundantIfStatement - if (child.getUserSetVisibility() != VISIBLE) { - // The view isn't set to be visible so skip changing its visibility - return false; - } - - return true; - } - - private boolean updateFabVisibilityForAppBarLayout(CoordinatorLayout parent, - AppBarLayout appBarLayout, FloatingActionButton child) { - if (!shouldUpdateVisibility(appBarLayout, child)) { - return false; - } - - if (mTmpRect == null) { - mTmpRect = new Rect(); - } - - // First, let's get the visible rect of the dependency - final Rect rect = mTmpRect; - ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect); - - if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) { - // If the anchor's bottom is below the seam, we'll animate our FAB out - child.hide(mInternalAutoHideListener, false); - } else { - // Else, we'll animate our FAB back in - child.show(mInternalAutoHideListener, false); - } - return true; - } - - private boolean updateFabVisibilityForBottomSheet(View bottomSheet, - FloatingActionButton child) { - if (!shouldUpdateVisibility(bottomSheet, child)) { - return false; - } - CoordinatorLayout.LayoutParams lp = - (CoordinatorLayout.LayoutParams) child.getLayoutParams(); - if (bottomSheet.getTop() < child.getHeight() / 2 + lp.topMargin) { - child.hide(mInternalAutoHideListener, false); - } else { - child.show(mInternalAutoHideListener, false); - } - return true; - } - - @Override - public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child, - int layoutDirection) { - // First, let's make sure that the visibility of the FAB is consistent - final List<View> dependencies = parent.getDependencies(child); - for (int i = 0, count = dependencies.size(); i < count; i++) { - final View dependency = dependencies.get(i); - if (dependency instanceof AppBarLayout) { - if (updateFabVisibilityForAppBarLayout( - parent, (AppBarLayout) dependency, child)) { - break; - } - } else if (isBottomSheet(dependency)) { - if (updateFabVisibilityForBottomSheet(dependency, child)) { - break; - } - } - } - // Now let the CoordinatorLayout lay out the FAB - parent.onLayoutChild(child, layoutDirection); - // Now offset it if needed - offsetIfNeeded(parent, child); - return true; - } - - @Override - public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, - @NonNull FloatingActionButton child, @NonNull Rect rect) { - // Since we offset so that any internal shadow padding isn't shown, we need to make - // sure that the shadow isn't used for any dodge inset calculations - final Rect shadowPadding = child.mShadowPadding; - rect.set(child.getLeft() + shadowPadding.left, - child.getTop() + shadowPadding.top, - child.getRight() - shadowPadding.right, - child.getBottom() - shadowPadding.bottom); - return true; - } - - /** - * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method - * offsets our layout position so that we're positioned correctly if we're on one of - * our parent's edges. - */ - private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) { - final Rect padding = fab.mShadowPadding; - - if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) { - final CoordinatorLayout.LayoutParams lp = - (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); - - int offsetTB = 0, offsetLR = 0; - - if (fab.getRight() >= parent.getWidth() - lp.rightMargin) { - // If we're on the right edge, shift it the right - offsetLR = padding.right; - } else if (fab.getLeft() <= lp.leftMargin) { - // If we're on the left edge, shift it the left - offsetLR = -padding.left; - } - if (fab.getBottom() >= parent.getHeight() - lp.bottomMargin) { - // If we're on the bottom edge, shift it down - offsetTB = padding.bottom; - } else if (fab.getTop() <= lp.topMargin) { - // If we're on the top edge, shift it up - offsetTB = -padding.top; - } - - if (offsetTB != 0) { - ViewCompat.offsetTopAndBottom(fab, offsetTB); - } - if (offsetLR != 0) { - ViewCompat.offsetLeftAndRight(fab, offsetLR); - } - } - } - } - - /** - * Returns the backward compatible elevation of the FloatingActionButton. - * - * @return the backward compatible elevation in pixels. - * @attr ref android.support.design.R.styleable#FloatingActionButton_elevation - * @see #setCompatElevation(float) - */ - public float getCompatElevation() { - return getImpl().getElevation(); - } - - /** - * Updates the backward compatible elevation of the FloatingActionButton. - * - * @param elevation The backward compatible elevation in pixels. - * @attr ref android.support.design.R.styleable#FloatingActionButton_elevation - * @see #getCompatElevation() - * @see #setUseCompatPadding(boolean) - */ - public void setCompatElevation(float elevation) { - getImpl().setElevation(elevation); - } - - private FloatingActionButtonImpl getImpl() { - if (mImpl == null) { - mImpl = createImpl(); - } - return mImpl; - } - - private FloatingActionButtonImpl createImpl() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return new FloatingActionButtonLollipop(this, new ShadowDelegateImpl()); - } else { - return new FloatingActionButtonImpl(this, new ShadowDelegateImpl()); - } - } - - private class ShadowDelegateImpl implements ShadowViewDelegate { - ShadowDelegateImpl() { - } - - @Override - public float getRadius() { - return getSizeDimension() / 2f; - } - - @Override - public void setShadowPadding(int left, int top, int right, int bottom) { - mShadowPadding.set(left, top, right, bottom); - setPadding(left + mImagePadding, top + mImagePadding, - right + mImagePadding, bottom + mImagePadding); - } - - @Override - public void setBackgroundDrawable(Drawable background) { - FloatingActionButton.super.setBackgroundDrawable(background); - } - - @Override - public boolean isCompatPaddingEnabled() { - return mCompatPadding; - } - } -} diff --git a/android/support/design/widget/FloatingActionButtonImpl.java b/android/support/design/widget/FloatingActionButtonImpl.java deleted file mode 100644 index 132cd81b..00000000 --- a/android/support/design/widget/FloatingActionButtonImpl.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * 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.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.LayerDrawable; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.support.design.R; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.view.ViewCompat; -import android.view.View; -import android.view.ViewTreeObserver; -import android.view.animation.Interpolator; - -@RequiresApi(14) -class FloatingActionButtonImpl { - static final Interpolator ANIM_INTERPOLATOR = AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR; - static final long PRESSED_ANIM_DURATION = 100; - static final long PRESSED_ANIM_DELAY = 100; - - static final int ANIM_STATE_NONE = 0; - static final int ANIM_STATE_HIDING = 1; - static final int ANIM_STATE_SHOWING = 2; - - int mAnimState = ANIM_STATE_NONE; - - private final StateListAnimator mStateListAnimator; - - ShadowDrawableWrapper mShadowDrawable; - - private float mRotation; - - Drawable mShapeDrawable; - Drawable mRippleDrawable; - CircularBorderDrawable mBorderDrawable; - Drawable mContentBackground; - - float mElevation; - float mPressedTranslationZ; - - interface InternalVisibilityChangedListener { - void onShown(); - void onHidden(); - } - - static final int SHOW_HIDE_ANIM_DURATION = 200; - - static final int[] PRESSED_ENABLED_STATE_SET = {android.R.attr.state_pressed, - android.R.attr.state_enabled}; - static final int[] FOCUSED_ENABLED_STATE_SET = {android.R.attr.state_focused, - android.R.attr.state_enabled}; - static final int[] ENABLED_STATE_SET = {android.R.attr.state_enabled}; - static final int[] EMPTY_STATE_SET = new int[0]; - - final VisibilityAwareImageButton mView; - final ShadowViewDelegate mShadowViewDelegate; - - private final Rect mTmpRect = new Rect(); - private ViewTreeObserver.OnPreDrawListener mPreDrawListener; - - FloatingActionButtonImpl(VisibilityAwareImageButton view, - ShadowViewDelegate shadowViewDelegate) { - mView = view; - mShadowViewDelegate = shadowViewDelegate; - - mStateListAnimator = new StateListAnimator(); - - // Elevate with translationZ when pressed or focused - mStateListAnimator.addState(PRESSED_ENABLED_STATE_SET, - createAnimator(new ElevateToTranslationZAnimation())); - mStateListAnimator.addState(FOCUSED_ENABLED_STATE_SET, - createAnimator(new ElevateToTranslationZAnimation())); - // Reset back to elevation by default - mStateListAnimator.addState(ENABLED_STATE_SET, - createAnimator(new ResetElevationAnimation())); - // Set to 0 when disabled - mStateListAnimator.addState(EMPTY_STATE_SET, - createAnimator(new DisabledElevationAnimation())); - - mRotation = mView.getRotation(); - } - - void setBackgroundDrawable(ColorStateList backgroundTint, - PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth) { - // Now we need to tint the original background with the tint, using - // an InsetDrawable if we have a border width - mShapeDrawable = DrawableCompat.wrap(createShapeDrawable()); - DrawableCompat.setTintList(mShapeDrawable, backgroundTint); - if (backgroundTintMode != null) { - DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode); - } - - // Now we created a mask Drawable which will be used for touch feedback. - GradientDrawable touchFeedbackShape = createShapeDrawable(); - - // We'll now wrap that touch feedback mask drawable with a ColorStateList. We do not need - // to inset for any border here as LayerDrawable will nest the padding for us - mRippleDrawable = DrawableCompat.wrap(touchFeedbackShape); - DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor)); - - final Drawable[] layers; - if (borderWidth > 0) { - mBorderDrawable = createBorderDrawable(borderWidth, backgroundTint); - layers = new Drawable[] {mBorderDrawable, mShapeDrawable, mRippleDrawable}; - } else { - mBorderDrawable = null; - layers = new Drawable[] {mShapeDrawable, mRippleDrawable}; - } - - mContentBackground = new LayerDrawable(layers); - - mShadowDrawable = new ShadowDrawableWrapper( - mView.getContext(), - mContentBackground, - mShadowViewDelegate.getRadius(), - mElevation, - mElevation + mPressedTranslationZ); - mShadowDrawable.setAddPaddingForCorners(false); - mShadowViewDelegate.setBackgroundDrawable(mShadowDrawable); - } - - void setBackgroundTintList(ColorStateList tint) { - if (mShapeDrawable != null) { - DrawableCompat.setTintList(mShapeDrawable, tint); - } - if (mBorderDrawable != null) { - mBorderDrawable.setBorderTint(tint); - } - } - - void setBackgroundTintMode(PorterDuff.Mode tintMode) { - if (mShapeDrawable != null) { - DrawableCompat.setTintMode(mShapeDrawable, tintMode); - } - } - - - void setRippleColor(int rippleColor) { - if (mRippleDrawable != null) { - DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor)); - } - } - - final void setElevation(float elevation) { - if (mElevation != elevation) { - mElevation = elevation; - onElevationsChanged(elevation, mPressedTranslationZ); - } - } - - float getElevation() { - return mElevation; - } - - final void setPressedTranslationZ(float translationZ) { - if (mPressedTranslationZ != translationZ) { - mPressedTranslationZ = translationZ; - onElevationsChanged(mElevation, translationZ); - } - } - - void onElevationsChanged(float elevation, float pressedTranslationZ) { - if (mShadowDrawable != null) { - mShadowDrawable.setShadowSize(elevation, elevation + mPressedTranslationZ); - updatePadding(); - } - } - - void onDrawableStateChanged(int[] state) { - mStateListAnimator.setState(state); - } - - void jumpDrawableToCurrentState() { - mStateListAnimator.jumpToCurrentState(); - } - - void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) { - if (isOrWillBeHidden()) { - // We either are or will soon be hidden, skip the call - return; - } - - mView.animate().cancel(); - - if (shouldAnimateVisibilityChange()) { - mAnimState = ANIM_STATE_HIDING; - - mView.animate() - .scaleX(0f) - .scaleY(0f) - .alpha(0f) - .setDuration(SHOW_HIDE_ANIM_DURATION) - .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR) - .setListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationStart(Animator animation) { - mView.internalSetVisibility(View.VISIBLE, fromUser); - mCancelled = false; - } - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - mAnimState = ANIM_STATE_NONE; - - if (!mCancelled) { - mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE, - fromUser); - if (listener != null) { - listener.onHidden(); - } - } - } - }); - } else { - // If the view isn't laid out, or we're in the editor, don't run the animation - mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE, fromUser); - if (listener != null) { - listener.onHidden(); - } - } - } - - void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) { - if (isOrWillBeShown()) { - // We either are or will soon be visible, skip the call - return; - } - - mView.animate().cancel(); - - if (shouldAnimateVisibilityChange()) { - mAnimState = ANIM_STATE_SHOWING; - - if (mView.getVisibility() != View.VISIBLE) { - // If the view isn't visible currently, we'll animate it from a single pixel - mView.setAlpha(0f); - mView.setScaleY(0f); - mView.setScaleX(0f); - } - - mView.animate() - .scaleX(1f) - .scaleY(1f) - .alpha(1f) - .setDuration(SHOW_HIDE_ANIM_DURATION) - .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mView.internalSetVisibility(View.VISIBLE, fromUser); - } - - @Override - public void onAnimationEnd(Animator animation) { - mAnimState = ANIM_STATE_NONE; - if (listener != null) { - listener.onShown(); - } - } - }); - } else { - mView.internalSetVisibility(View.VISIBLE, fromUser); - mView.setAlpha(1f); - mView.setScaleY(1f); - mView.setScaleX(1f); - if (listener != null) { - listener.onShown(); - } - } - } - - final Drawable getContentBackground() { - return mContentBackground; - } - - void onCompatShadowChanged() { - // Ignore pre-v21 - } - - final void updatePadding() { - Rect rect = mTmpRect; - getPadding(rect); - onPaddingUpdated(rect); - mShadowViewDelegate.setShadowPadding(rect.left, rect.top, rect.right, rect.bottom); - } - - void getPadding(Rect rect) { - mShadowDrawable.getPadding(rect); - } - - void onPaddingUpdated(Rect padding) {} - - void onAttachedToWindow() { - if (requirePreDrawListener()) { - ensurePreDrawListener(); - mView.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); - } - } - - void onDetachedFromWindow() { - if (mPreDrawListener != null) { - mView.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); - mPreDrawListener = null; - } - } - - boolean requirePreDrawListener() { - return true; - } - - CircularBorderDrawable createBorderDrawable(int borderWidth, ColorStateList backgroundTint) { - final Context context = mView.getContext(); - CircularBorderDrawable borderDrawable = newCircularDrawable(); - borderDrawable.setGradientColors( - ContextCompat.getColor(context, R.color.design_fab_stroke_top_outer_color), - ContextCompat.getColor(context, R.color.design_fab_stroke_top_inner_color), - ContextCompat.getColor(context, R.color.design_fab_stroke_end_inner_color), - ContextCompat.getColor(context, R.color.design_fab_stroke_end_outer_color)); - borderDrawable.setBorderWidth(borderWidth); - borderDrawable.setBorderTint(backgroundTint); - return borderDrawable; - } - - CircularBorderDrawable newCircularDrawable() { - return new CircularBorderDrawable(); - } - - void onPreDraw() { - final float rotation = mView.getRotation(); - if (mRotation != rotation) { - mRotation = rotation; - updateFromViewRotation(); - } - } - - private void ensurePreDrawListener() { - if (mPreDrawListener == null) { - mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - FloatingActionButtonImpl.this.onPreDraw(); - return true; - } - }; - } - } - - GradientDrawable createShapeDrawable() { - GradientDrawable d = newGradientDrawableForShape(); - d.setShape(GradientDrawable.OVAL); - d.setColor(Color.WHITE); - return d; - } - - GradientDrawable newGradientDrawableForShape() { - return new GradientDrawable(); - } - - boolean isOrWillBeShown() { - if (mView.getVisibility() != View.VISIBLE) { - // If we not currently visible, return true if we're animating to be shown - return mAnimState == ANIM_STATE_SHOWING; - } else { - // Otherwise if we're visible, return true if we're not animating to be hidden - return mAnimState != ANIM_STATE_HIDING; - } - } - - boolean isOrWillBeHidden() { - if (mView.getVisibility() == View.VISIBLE) { - // If we currently visible, return true if we're animating to be hidden - return mAnimState == ANIM_STATE_HIDING; - } else { - // Otherwise if we're not visible, return true if we're not animating to be shown - return mAnimState != ANIM_STATE_SHOWING; - } - } - - private ValueAnimator createAnimator(@NonNull ShadowAnimatorImpl impl) { - final ValueAnimator animator = new ValueAnimator(); - animator.setInterpolator(ANIM_INTERPOLATOR); - animator.setDuration(PRESSED_ANIM_DURATION); - animator.addListener(impl); - animator.addUpdateListener(impl); - animator.setFloatValues(0, 1); - return animator; - } - - private abstract class ShadowAnimatorImpl extends AnimatorListenerAdapter - implements ValueAnimator.AnimatorUpdateListener { - private boolean mValidValues; - private float mShadowSizeStart; - private float mShadowSizeEnd; - - @Override - public void onAnimationUpdate(ValueAnimator animator) { - if (!mValidValues) { - mShadowSizeStart = mShadowDrawable.getShadowSize(); - mShadowSizeEnd = getTargetShadowSize(); - mValidValues = true; - } - - mShadowDrawable.setShadowSize(mShadowSizeStart - + ((mShadowSizeEnd - mShadowSizeStart) * animator.getAnimatedFraction())); - } - - @Override - public void onAnimationEnd(Animator animator) { - mShadowDrawable.setShadowSize(mShadowSizeEnd); - mValidValues = false; - } - - /** - * @return the shadow size we want to animate to. - */ - protected abstract float getTargetShadowSize(); - } - - private class ResetElevationAnimation extends ShadowAnimatorImpl { - ResetElevationAnimation() { - } - - @Override - protected float getTargetShadowSize() { - return mElevation; - } - } - - private class ElevateToTranslationZAnimation extends ShadowAnimatorImpl { - ElevateToTranslationZAnimation() { - } - - @Override - protected float getTargetShadowSize() { - return mElevation + mPressedTranslationZ; - } - } - - private class DisabledElevationAnimation extends ShadowAnimatorImpl { - DisabledElevationAnimation() { - } - - @Override - protected float getTargetShadowSize() { - return 0f; - } - } - - private static ColorStateList createColorStateList(int selectedColor) { - final int[][] states = new int[3][]; - final int[] colors = new int[3]; - int i = 0; - - states[i] = FOCUSED_ENABLED_STATE_SET; - colors[i] = selectedColor; - i++; - - states[i] = PRESSED_ENABLED_STATE_SET; - colors[i] = selectedColor; - i++; - - // Default enabled state - states[i] = new int[0]; - colors[i] = Color.TRANSPARENT; - i++; - - return new ColorStateList(states, colors); - } - - private boolean shouldAnimateVisibilityChange() { - return ViewCompat.isLaidOut(mView) && !mView.isInEditMode(); - } - - private void updateFromViewRotation() { - if (Build.VERSION.SDK_INT == 19) { - // KitKat seems to have an issue with views which are rotated with angles which are - // not divisible by 90. Worked around by moving to software rendering in these cases. - if ((mRotation % 90) != 0) { - if (mView.getLayerType() != View.LAYER_TYPE_SOFTWARE) { - mView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - } else { - if (mView.getLayerType() != View.LAYER_TYPE_NONE) { - mView.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - } - - // Offset any View rotation - if (mShadowDrawable != null) { - mShadowDrawable.setRotation(-mRotation); - } - if (mBorderDrawable != null) { - mBorderDrawable.setRotation(-mRotation); - } - } -} diff --git a/android/support/design/widget/FloatingActionButtonLollipop.java b/android/support/design/widget/FloatingActionButtonLollipop.java deleted file mode 100644 index 0df83da5..00000000 --- a/android/support/design/widget/FloatingActionButtonLollipop.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * 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.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.StateListAnimator; -import android.content.res.ColorStateList; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.InsetDrawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.RippleDrawable; -import android.os.Build; -import android.support.annotation.RequiresApi; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.view.View; - -import java.util.ArrayList; -import java.util.List; - -@RequiresApi(21) -class FloatingActionButtonLollipop extends FloatingActionButtonImpl { - - private InsetDrawable mInsetDrawable; - - FloatingActionButtonLollipop(VisibilityAwareImageButton view, - ShadowViewDelegate shadowViewDelegate) { - super(view, shadowViewDelegate); - } - - @Override - void setBackgroundDrawable(ColorStateList backgroundTint, - PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth) { - // Now we need to tint the shape background with the tint - mShapeDrawable = DrawableCompat.wrap(createShapeDrawable()); - DrawableCompat.setTintList(mShapeDrawable, backgroundTint); - if (backgroundTintMode != null) { - DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode); - } - - final Drawable rippleContent; - if (borderWidth > 0) { - mBorderDrawable = createBorderDrawable(borderWidth, backgroundTint); - rippleContent = new LayerDrawable(new Drawable[]{mBorderDrawable, mShapeDrawable}); - } else { - mBorderDrawable = null; - rippleContent = mShapeDrawable; - } - - mRippleDrawable = new RippleDrawable(ColorStateList.valueOf(rippleColor), - rippleContent, null); - - mContentBackground = mRippleDrawable; - - mShadowViewDelegate.setBackgroundDrawable(mRippleDrawable); - } - - @Override - void setRippleColor(int rippleColor) { - if (mRippleDrawable instanceof RippleDrawable) { - ((RippleDrawable) mRippleDrawable).setColor(ColorStateList.valueOf(rippleColor)); - } else { - super.setRippleColor(rippleColor); - } - } - - @Override - void onElevationsChanged(final float elevation, final float pressedTranslationZ) { - if (Build.VERSION.SDK_INT == 21) { - // Animations produce NPE in version 21. Bluntly set the values instead (matching the - // logic in the animations below). - if (mView.isEnabled()) { - mView.setElevation(elevation); - if (mView.isFocused() || mView.isPressed()) { - mView.setTranslationZ(pressedTranslationZ); - } else { - mView.setTranslationZ(0); - } - } else { - mView.setElevation(0); - mView.setTranslationZ(0); - } - } else { - final StateListAnimator stateListAnimator = new StateListAnimator(); - - // Animate elevation and translationZ to our values when pressed - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(mView, "elevation", elevation).setDuration(0)) - .with(ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, pressedTranslationZ) - .setDuration(PRESSED_ANIM_DURATION)); - set.setInterpolator(ANIM_INTERPOLATOR); - stateListAnimator.addState(PRESSED_ENABLED_STATE_SET, set); - - // Same deal for when we're focused - set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(mView, "elevation", elevation).setDuration(0)) - .with(ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, pressedTranslationZ) - .setDuration(PRESSED_ANIM_DURATION)); - set.setInterpolator(ANIM_INTERPOLATOR); - stateListAnimator.addState(FOCUSED_ENABLED_STATE_SET, set); - - // Animate translationZ to 0 if not pressed - set = new AnimatorSet(); - List<Animator> animators = new ArrayList<>(); - animators.add(ObjectAnimator.ofFloat(mView, "elevation", elevation).setDuration(0)); - if (Build.VERSION.SDK_INT >= 22 && Build.VERSION.SDK_INT <= 24) { - // This is a no-op animation which exists here only for introducing the duration - // because setting the delay (on the next animation) via "setDelay" or "after" - // can trigger a NPE between android versions 22 and 24 (due to a framework - // bug). The issue has been fixed in version 25. - animators.add(ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, - mView.getTranslationZ()).setDuration(PRESSED_ANIM_DELAY)); - } - animators.add(ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, 0f) - .setDuration(PRESSED_ANIM_DURATION)); - set.playSequentially(animators.toArray(new ObjectAnimator[0])); - set.setInterpolator(ANIM_INTERPOLATOR); - stateListAnimator.addState(ENABLED_STATE_SET, set); - - // Animate everything to 0 when disabled - set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(mView, "elevation", 0f).setDuration(0)) - .with(ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, 0f).setDuration(0)); - set.setInterpolator(ANIM_INTERPOLATOR); - stateListAnimator.addState(EMPTY_STATE_SET, set); - - mView.setStateListAnimator(stateListAnimator); - } - - if (mShadowViewDelegate.isCompatPaddingEnabled()) { - updatePadding(); - } - } - - @Override - public float getElevation() { - return mView.getElevation(); - } - - @Override - void onCompatShadowChanged() { - updatePadding(); - } - - @Override - void onPaddingUpdated(Rect padding) { - if (mShadowViewDelegate.isCompatPaddingEnabled()) { - mInsetDrawable = new InsetDrawable(mRippleDrawable, - padding.left, padding.top, padding.right, padding.bottom); - mShadowViewDelegate.setBackgroundDrawable(mInsetDrawable); - } else { - mShadowViewDelegate.setBackgroundDrawable(mRippleDrawable); - } - } - - @Override - void onDrawableStateChanged(int[] state) { - // no-op - } - - @Override - void jumpDrawableToCurrentState() { - // no-op - } - - @Override - boolean requirePreDrawListener() { - return false; - } - - @Override - CircularBorderDrawable newCircularDrawable() { - return new CircularBorderDrawableLollipop(); - } - - @Override - GradientDrawable newGradientDrawableForShape() { - return new AlwaysStatefulGradientDrawable(); - } - - @Override - void getPadding(Rect rect) { - if (mShadowViewDelegate.isCompatPaddingEnabled()) { - final float radius = mShadowViewDelegate.getRadius(); - final float maxShadowSize = getElevation() + mPressedTranslationZ; - final int hPadding = (int) Math.ceil( - ShadowDrawableWrapper.calculateHorizontalPadding(maxShadowSize, radius, false)); - final int vPadding = (int) Math.ceil( - ShadowDrawableWrapper.calculateVerticalPadding(maxShadowSize, radius, false)); - rect.set(hPadding, vPadding, hPadding, vPadding); - } else { - rect.set(0, 0, 0, 0); - } - } - - /** - * LayerDrawable on L+ caches its isStateful() state and doesn't refresh it, - * meaning that if we apply a tint to one of its children, the parent doesn't become - * stateful and the tint doesn't work for state changes. We workaround it by saying that we - * are always stateful. If we don't have a stateful tint, the change is ignored anyway. - */ - static class AlwaysStatefulGradientDrawable extends GradientDrawable { - @Override - public boolean isStateful() { - return true; - } - } -} diff --git a/android/support/design/widget/HeaderBehavior.java b/android/support/design/widget/HeaderBehavior.java deleted file mode 100644 index a5d0edf6..00000000 --- a/android/support/design/widget/HeaderBehavior.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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.content.Context; -import android.support.design.widget.CoordinatorLayout.Behavior; -import android.support.v4.math.MathUtils; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.OverScroller; - -/** - * The {@link Behavior} for a view that sits vertically above scrolling a view. - * See {@link HeaderScrollingViewBehavior}. - */ -abstract class HeaderBehavior<V extends View> extends ViewOffsetBehavior<V> { - - private static final int INVALID_POINTER = -1; - - private Runnable mFlingRunnable; - OverScroller mScroller; - - private boolean mIsBeingDragged; - private int mActivePointerId = INVALID_POINTER; - private int mLastMotionY; - private int mTouchSlop = -1; - private VelocityTracker mVelocityTracker; - - public HeaderBehavior() {} - - public HeaderBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { - if (mTouchSlop < 0) { - mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop(); - } - - final int action = ev.getAction(); - - // Shortcut since we're being dragged - if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) { - return true; - } - - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - mIsBeingDragged = false; - final int x = (int) ev.getX(); - final int y = (int) ev.getY(); - if (canDragView(child) && parent.isPointInChildBounds(child, x, y)) { - mLastMotionY = y; - mActivePointerId = ev.getPointerId(0); - ensureVelocityTracker(); - } - break; - } - - case MotionEvent.ACTION_MOVE: { - final int activePointerId = mActivePointerId; - if (activePointerId == INVALID_POINTER) { - // If we don't have a valid id, the touch down wasn't on content. - break; - } - final int pointerIndex = ev.findPointerIndex(activePointerId); - if (pointerIndex == -1) { - break; - } - - final int y = (int) ev.getY(pointerIndex); - final int yDiff = Math.abs(y - mLastMotionY); - if (yDiff > mTouchSlop) { - mIsBeingDragged = true; - mLastMotionY = y; - } - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - mIsBeingDragged = false; - mActivePointerId = INVALID_POINTER; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - break; - } - } - - if (mVelocityTracker != null) { - mVelocityTracker.addMovement(ev); - } - - return mIsBeingDragged; - } - - @Override - public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { - if (mTouchSlop < 0) { - mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop(); - } - - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - final int x = (int) ev.getX(); - final int y = (int) ev.getY(); - - if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) { - mLastMotionY = y; - mActivePointerId = ev.getPointerId(0); - ensureVelocityTracker(); - } else { - return false; - } - break; - } - - case MotionEvent.ACTION_MOVE: { - final int activePointerIndex = ev.findPointerIndex(mActivePointerId); - if (activePointerIndex == -1) { - return false; - } - - final int y = (int) ev.getY(activePointerIndex); - int dy = mLastMotionY - y; - - if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) { - mIsBeingDragged = true; - if (dy > 0) { - dy -= mTouchSlop; - } else { - dy += mTouchSlop; - } - } - - if (mIsBeingDragged) { - mLastMotionY = y; - // We're being dragged so scroll the ABL - scroll(parent, child, dy, getMaxDragOffset(child), 0); - } - break; - } - - case MotionEvent.ACTION_UP: - if (mVelocityTracker != null) { - mVelocityTracker.addMovement(ev); - mVelocityTracker.computeCurrentVelocity(1000); - float yvel = mVelocityTracker.getYVelocity(mActivePointerId); - fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel); - } - // $FALLTHROUGH - case MotionEvent.ACTION_CANCEL: { - mIsBeingDragged = false; - mActivePointerId = INVALID_POINTER; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - break; - } - } - - if (mVelocityTracker != null) { - mVelocityTracker.addMovement(ev); - } - - return true; - } - - int setHeaderTopBottomOffset(CoordinatorLayout parent, V header, int newOffset) { - return setHeaderTopBottomOffset(parent, header, newOffset, - Integer.MIN_VALUE, Integer.MAX_VALUE); - } - - int setHeaderTopBottomOffset(CoordinatorLayout parent, V header, int newOffset, - int minOffset, int maxOffset) { - final int curOffset = getTopAndBottomOffset(); - int consumed = 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.clamp(newOffset, minOffset, maxOffset); - - if (curOffset != newOffset) { - setTopAndBottomOffset(newOffset); - // Update how much dy we have consumed - consumed = curOffset - newOffset; - } - } - - return consumed; - } - - int getTopBottomOffsetForScrollingSibling() { - return getTopAndBottomOffset(); - } - - final int scroll(CoordinatorLayout coordinatorLayout, V header, - int dy, int minOffset, int maxOffset) { - return setHeaderTopBottomOffset(coordinatorLayout, header, - getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset); - } - - final boolean fling(CoordinatorLayout coordinatorLayout, V layout, int minOffset, - int maxOffset, float velocityY) { - if (mFlingRunnable != null) { - layout.removeCallbacks(mFlingRunnable); - mFlingRunnable = null; - } - - if (mScroller == null) { - mScroller = new OverScroller(layout.getContext()); - } - - mScroller.fling( - 0, getTopAndBottomOffset(), // 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 { - onFlingFinished(coordinatorLayout, layout); - return false; - } - } - - /** - * Called when a fling has finished, or the fling was initiated but there wasn't enough - * velocity to start it. - */ - void onFlingFinished(CoordinatorLayout parent, V layout) { - // no-op - } - - /** - * Return true if the view can be dragged. - */ - boolean canDragView(V view) { - return false; - } - - /** - * Returns the maximum px offset when {@code view} is being dragged. - */ - int getMaxDragOffset(V view) { - return -view.getHeight(); - } - - int getScrollRangeForDragFling(V view) { - return view.getHeight(); - } - - private void ensureVelocityTracker() { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - } - - private class FlingRunnable implements Runnable { - private final CoordinatorLayout mParent; - private final V mLayout; - - FlingRunnable(CoordinatorLayout parent, V layout) { - mParent = parent; - mLayout = layout; - } - - @Override - public void run() { - if (mLayout != null && mScroller != null) { - if (mScroller.computeScrollOffset()) { - setHeaderTopBottomOffset(mParent, mLayout, mScroller.getCurrY()); - // Post ourselves so that we run on the next animation - ViewCompat.postOnAnimation(mLayout, this); - } else { - onFlingFinished(mParent, mLayout); - } - } - } - } -} diff --git a/android/support/design/widget/HeaderScrollingViewBehavior.java b/android/support/design/widget/HeaderScrollingViewBehavior.java deleted file mode 100644 index 81ddde54..00000000 --- a/android/support/design/widget/HeaderScrollingViewBehavior.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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.content.Context; -import android.graphics.Rect; -import android.support.design.widget.CoordinatorLayout.Behavior; -import android.support.v4.math.MathUtils; -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 java.util.List; - -/** - * The {@link Behavior} for a scrolling view that is positioned vertically below another view. - * See {@link HeaderBehavior}. - */ -abstract class HeaderScrollingViewBehavior extends ViewOffsetBehavior<View> { - - final Rect mTempRect1 = new Rect(); - final Rect mTempRect2 = new Rect(); - - private int mVerticalLayoutGap = 0; - private int mOverlayTop; - - public HeaderScrollingViewBehavior() {} - - public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onMeasureChild(CoordinatorLayout parent, View child, - int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, - int heightUsed) { - final int childLpHeight = child.getLayoutParams().height; - if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT - || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { - // If the menu's height is set to match_parent/wrap_content then measure it - // with the maximum visible height - - final List<View> dependencies = parent.getDependencies(child); - final View header = findFirstDependency(dependencies); - if (header != null) { - if (ViewCompat.getFitsSystemWindows(header) - && !ViewCompat.getFitsSystemWindows(child)) { - // If the header is fitting system windows then we need to also, - // otherwise we'll get CoL's compatible measuring - ViewCompat.setFitsSystemWindows(child, true); - - if (ViewCompat.getFitsSystemWindows(child)) { - // If the set succeeded, trigger a new layout and return true - child.requestLayout(); - return true; - } - } - - int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec); - if (availableHeight == 0) { - // If the measure spec doesn't specify a size, use the current height - availableHeight = parent.getHeight(); - } - - final int height = availableHeight - header.getMeasuredHeight() - + getScrollRange(header); - final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, - childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT - ? View.MeasureSpec.EXACTLY - : View.MeasureSpec.AT_MOST); - - // Now measure the scrolling view with the correct height - parent.onMeasureChild(child, parentWidthMeasureSpec, - widthUsed, heightMeasureSpec, heightUsed); - - return true; - } - } - return false; - } - - @Override - protected void layoutChild(final CoordinatorLayout parent, final View child, - final int layoutDirection) { - final List<View> dependencies = parent.getDependencies(child); - final View header = findFirstDependency(dependencies); - - if (header != null) { - final CoordinatorLayout.LayoutParams lp = - (CoordinatorLayout.LayoutParams) child.getLayoutParams(); - final Rect available = mTempRect1; - available.set(parent.getPaddingLeft() + lp.leftMargin, - header.getBottom() + lp.topMargin, - parent.getWidth() - parent.getPaddingRight() - lp.rightMargin, - parent.getHeight() + header.getBottom() - - parent.getPaddingBottom() - lp.bottomMargin); - - final WindowInsetsCompat parentInsets = parent.getLastWindowInsets(); - if (parentInsets != null && ViewCompat.getFitsSystemWindows(parent) - && !ViewCompat.getFitsSystemWindows(child)) { - // If we're set to handle insets but this child isn't, then it has been measured as - // if there are no insets. We need to lay it out to match horizontally. - // Top and bottom and already handled in the logic above - available.left += parentInsets.getSystemWindowInsetLeft(); - available.right -= parentInsets.getSystemWindowInsetRight(); - } - - final Rect out = mTempRect2; - GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), - child.getMeasuredHeight(), available, out, layoutDirection); - - final int overlap = getOverlapPixelsForOffset(header); - - child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap); - mVerticalLayoutGap = out.top - header.getBottom(); - } else { - // If we don't have a dependency, let super handle it - super.layoutChild(parent, child, layoutDirection); - mVerticalLayoutGap = 0; - } - } - - float getOverlapRatioForOffset(final View header) { - return 1f; - } - - final int getOverlapPixelsForOffset(final View header) { - return mOverlayTop == 0 ? 0 : MathUtils.clamp( - (int) (getOverlapRatioForOffset(header) * mOverlayTop), 0, mOverlayTop); - } - - private static int resolveGravity(int gravity) { - return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity; - } - - abstract View findFirstDependency(List<View> views); - - int getScrollRange(View v) { - return v.getMeasuredHeight(); - } - - /** - * The gap between the top of the scrolling view and the bottom of the header layout in pixels. - */ - final int getVerticalLayoutGap() { - return mVerticalLayoutGap; - } - - /** - * Set the distance that this view should overlap any {@link AppBarLayout}. - * - * @param overlayTop the distance in px - */ - public final void setOverlayTop(int overlayTop) { - mOverlayTop = overlayTop; - } - - /** - * Returns the distance that this view should overlap any {@link AppBarLayout}. - */ - public final int getOverlayTop() { - return mOverlayTop; - } -}
\ No newline at end of file diff --git a/android/support/design/widget/NavigationView.java b/android/support/design/widget/NavigationView.java deleted file mode 100644 index 8fc8c76b..00000000 --- a/android/support/design/widget/NavigationView.java +++ /dev/null @@ -1,494 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.DrawableRes; -import android.support.annotation.IdRes; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.StyleRes; -import android.support.design.R; -import android.support.design.internal.NavigationMenu; -import android.support.design.internal.NavigationMenuPresenter; -import android.support.design.internal.ScrimInsetsFrameLayout; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.AbsSavedState; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.WindowInsetsCompat; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.view.SupportMenuInflater; -import android.support.v7.view.menu.MenuBuilder; -import android.support.v7.view.menu.MenuItemImpl; -import android.support.v7.widget.TintTypedArray; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; - -/** - * Represents a standard navigation menu for application. The menu contents can be populated - * by a menu resource file. - * <p>NavigationView is typically placed inside a {@link android.support.v4.widget.DrawerLayout}. - * </p> - * <pre> - * <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" - * xmlns:app="http://schemas.android.com/apk/res-auto" - * android:id="@+id/drawer_layout" - * android:layout_width="match_parent" - * android:layout_height="match_parent" - * android:fitsSystemWindows="true"> - * - * <!-- Your contents --> - * - * <android.support.design.widget.NavigationView - * android:id="@+id/navigation" - * android:layout_width="wrap_content" - * android:layout_height="match_parent" - * android:layout_gravity="start" - * app:menu="@menu/my_navigation_items" /> - * </android.support.v4.widget.DrawerLayout> - * </pre> - */ -public class NavigationView extends ScrimInsetsFrameLayout { - - private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; - private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled}; - - private static final int PRESENTER_NAVIGATION_VIEW_ID = 1; - - private final NavigationMenu mMenu; - private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter(); - - OnNavigationItemSelectedListener mListener; - private int mMaxWidth; - - private MenuInflater mMenuInflater; - - public NavigationView(Context context) { - this(context, null); - } - - public NavigationView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - ThemeUtils.checkAppCompatTheme(context); - - // Create the menu - mMenu = new NavigationMenu(context); - - // Custom attributes - TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, - R.styleable.NavigationView, defStyleAttr, - R.style.Widget_Design_NavigationView); - - ViewCompat.setBackground( - this, a.getDrawable(R.styleable.NavigationView_android_background)); - if (a.hasValue(R.styleable.NavigationView_elevation)) { - ViewCompat.setElevation(this, a.getDimensionPixelSize( - R.styleable.NavigationView_elevation, 0)); - } - ViewCompat.setFitsSystemWindows(this, - a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false)); - - mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0); - - final ColorStateList itemIconTint; - if (a.hasValue(R.styleable.NavigationView_itemIconTint)) { - itemIconTint = a.getColorStateList(R.styleable.NavigationView_itemIconTint); - } else { - itemIconTint = createDefaultColorStateList(android.R.attr.textColorSecondary); - } - - boolean textAppearanceSet = false; - int textAppearance = 0; - if (a.hasValue(R.styleable.NavigationView_itemTextAppearance)) { - textAppearance = a.getResourceId(R.styleable.NavigationView_itemTextAppearance, 0); - textAppearanceSet = true; - } - - ColorStateList itemTextColor = null; - if (a.hasValue(R.styleable.NavigationView_itemTextColor)) { - itemTextColor = a.getColorStateList(R.styleable.NavigationView_itemTextColor); - } - - if (!textAppearanceSet && itemTextColor == null) { - // If there isn't a text appearance set, we'll use a default text color - itemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary); - } - - final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground); - - mMenu.setCallback(new MenuBuilder.Callback() { - @Override - public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { - return mListener != null && mListener.onNavigationItemSelected(item); - } - - @Override - public void onMenuModeChange(MenuBuilder menu) {} - }); - mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID); - mPresenter.initForMenu(context, mMenu); - mPresenter.setItemIconTintList(itemIconTint); - if (textAppearanceSet) { - mPresenter.setItemTextAppearance(textAppearance); - } - mPresenter.setItemTextColor(itemTextColor); - mPresenter.setItemBackground(itemBackground); - mMenu.addMenuPresenter(mPresenter); - addView((View) mPresenter.getMenuView(this)); - - if (a.hasValue(R.styleable.NavigationView_menu)) { - inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0)); - } - - if (a.hasValue(R.styleable.NavigationView_headerLayout)) { - inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0)); - } - - a.recycle(); - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState state = new SavedState(superState); - state.menuState = new Bundle(); - mMenu.savePresenterStates(state.menuState); - return state; - } - - @Override - protected void onRestoreInstanceState(Parcelable savedState) { - if (!(savedState instanceof SavedState)) { - super.onRestoreInstanceState(savedState); - return; - } - SavedState state = (SavedState) savedState; - super.onRestoreInstanceState(state.getSuperState()); - mMenu.restorePresenterStates(state.menuState); - } - - /** - * Set a listener that will be notified when a menu item is selected. - * - * @param listener The listener to notify - */ - public void setNavigationItemSelectedListener( - @Nullable OnNavigationItemSelectedListener listener) { - mListener = listener; - } - - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - switch (MeasureSpec.getMode(widthSpec)) { - case MeasureSpec.EXACTLY: - // Nothing to do - break; - case MeasureSpec.AT_MOST: - widthSpec = MeasureSpec.makeMeasureSpec( - Math.min(MeasureSpec.getSize(widthSpec), mMaxWidth), MeasureSpec.EXACTLY); - break; - case MeasureSpec.UNSPECIFIED: - widthSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY); - break; - } - // Let super sort out the height - super.onMeasure(widthSpec, heightSpec); - } - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @Override - protected void onInsetsChanged(WindowInsetsCompat insets) { - mPresenter.dispatchApplyWindowInsets(insets); - } - - /** - * Inflate a menu resource into this navigation view. - * - * <p>Existing items in the menu will not be modified or removed.</p> - * - * @param resId ID of a menu resource to inflate - */ - public void inflateMenu(int resId) { - mPresenter.setUpdateSuspended(true); - getMenuInflater().inflate(resId, mMenu); - mPresenter.setUpdateSuspended(false); - mPresenter.updateMenuView(false); - } - - /** - * Returns the {@link Menu} instance associated with this navigation view. - */ - public Menu getMenu() { - return mMenu; - } - - /** - * Inflates a View and add it as a header of the navigation menu. - * - * @param res The layout resource ID. - * @return a newly inflated View. - */ - public View inflateHeaderView(@LayoutRes int res) { - return mPresenter.inflateHeaderView(res); - } - - /** - * Adds a View as a header of the navigation menu. - * - * @param view The view to be added as a header of the navigation menu. - */ - public void addHeaderView(@NonNull View view) { - mPresenter.addHeaderView(view); - } - - /** - * Removes a previously-added header view. - * - * @param view The view to remove - */ - public void removeHeaderView(@NonNull View view) { - mPresenter.removeHeaderView(view); - } - - /** - * Gets the number of headers in this NavigationView. - * - * @return A positive integer representing the number of headers. - */ - public int getHeaderCount() { - return mPresenter.getHeaderCount(); - } - - /** - * Gets the header view at the specified position. - * - * @param index The position at which to get the view from. - * @return The header view the specified position or null if the position does not exist in this - * NavigationView. - */ - public View getHeaderView(int index) { - return mPresenter.getHeaderView(index); - } - - /** - * Returns the tint which is applied to our menu items' icons. - * - * @see #setItemIconTintList(ColorStateList) - * - * @attr ref R.styleable#NavigationView_itemIconTint - */ - @Nullable - public ColorStateList getItemIconTintList() { - return mPresenter.getItemTintList(); - } - - /** - * Set the tint which is applied to our menu items' icons. - * - * @param tint the tint to apply. - * - * @attr ref R.styleable#NavigationView_itemIconTint - */ - public void setItemIconTintList(@Nullable ColorStateList tint) { - mPresenter.setItemIconTintList(tint); - } - - /** - * Returns the tint which is applied to our menu items' icons. - * - * @see #setItemTextColor(ColorStateList) - * - * @attr ref R.styleable#NavigationView_itemTextColor - */ - @Nullable - public ColorStateList getItemTextColor() { - return mPresenter.getItemTextColor(); - } - - /** - * Set the text color to be used on our menu items. - * - * @see #getItemTextColor() - * - * @attr ref R.styleable#NavigationView_itemTextColor - */ - public void setItemTextColor(@Nullable ColorStateList textColor) { - mPresenter.setItemTextColor(textColor); - } - - /** - * Returns the background drawable for our menu items. - * - * @see #setItemBackgroundResource(int) - * - * @attr ref R.styleable#NavigationView_itemBackground - */ - @Nullable - public Drawable getItemBackground() { - return mPresenter.getItemBackground(); - } - - /** - * Set the background of our menu items to the given resource. - * - * @param resId The identifier of the resource. - * - * @attr ref R.styleable#NavigationView_itemBackground - */ - public void setItemBackgroundResource(@DrawableRes int resId) { - setItemBackground(ContextCompat.getDrawable(getContext(), resId)); - } - - /** - * Set the background of our menu items to a given resource. The resource should refer to - * a Drawable object or null to use the default background set on this navigation menu. - * - * @attr ref R.styleable#NavigationView_itemBackground - */ - public void setItemBackground(@Nullable Drawable itemBackground) { - mPresenter.setItemBackground(itemBackground); - } - - /** - * Sets the currently checked item in this navigation menu. - * - * @param id The item ID of the currently checked item. - */ - public void setCheckedItem(@IdRes int id) { - MenuItem item = mMenu.findItem(id); - if (item != null) { - mPresenter.setCheckedItem((MenuItemImpl) item); - } - } - - /** - * Set the text appearance of the menu items to a given resource. - * - * @attr ref R.styleable#NavigationView_itemTextAppearance - */ - public void setItemTextAppearance(@StyleRes int resId) { - mPresenter.setItemTextAppearance(resId); - } - - private MenuInflater getMenuInflater() { - if (mMenuInflater == null) { - mMenuInflater = new SupportMenuInflater(getContext()); - } - return mMenuInflater; - } - - private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) { - final TypedValue value = new TypedValue(); - if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) { - return null; - } - ColorStateList baseColor = AppCompatResources.getColorStateList( - getContext(), value.resourceId); - if (!getContext().getTheme().resolveAttribute( - android.support.v7.appcompat.R.attr.colorPrimary, value, true)) { - return null; - } - int colorPrimary = value.data; - int defaultColor = baseColor.getDefaultColor(); - return new ColorStateList(new int[][]{ - DISABLED_STATE_SET, - CHECKED_STATE_SET, - EMPTY_STATE_SET - }, new int[]{ - baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), - colorPrimary, - defaultColor - }); - } - - /** - * Listener for handling events on navigation items. - */ - public interface OnNavigationItemSelectedListener { - - /** - * Called when an item in the navigation menu is selected. - * - * @param item The selected item - * - * @return true to display the item as the selected item - */ - public boolean onNavigationItemSelected(@NonNull MenuItem item); - } - - /** - * User interface state that is stored by NavigationView for implementing - * onSaveInstanceState(). - */ - public static class SavedState extends AbsSavedState { - public Bundle menuState; - - public SavedState(Parcel in, ClassLoader loader) { - super(in, loader); - menuState = in.readBundle(loader); - } - - public SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeBundle(menuState); - } - - public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel in, ClassLoader loader) { - return new SavedState(in, loader); - } - - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in, null); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - -} diff --git a/android/support/design/widget/ShadowDrawableWrapper.java b/android/support/design/widget/ShadowDrawableWrapper.java deleted file mode 100644 index dfb8e1d6..00000000 --- a/android/support/design/widget/ShadowDrawableWrapper.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright (C) 2014 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.content.Context; -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.RadialGradient; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.drawable.Drawable; -import android.support.design.R; -import android.support.v4.content.ContextCompat; -import android.support.v7.graphics.drawable.DrawableWrapper; - -/** - * A {@link android.graphics.drawable.Drawable} which wraps another drawable and - * draws a shadow around it. - */ -class ShadowDrawableWrapper extends DrawableWrapper { - // used to calculate content padding - static final double COS_45 = Math.cos(Math.toRadians(45)); - - static final float SHADOW_MULTIPLIER = 1.5f; - - static final float SHADOW_TOP_SCALE = 0.25f; - static final float SHADOW_HORIZ_SCALE = 0.5f; - static final float SHADOW_BOTTOM_SCALE = 1f; - - final Paint mCornerShadowPaint; - final Paint mEdgeShadowPaint; - - final RectF mContentBounds; - - float mCornerRadius; - - Path mCornerShadowPath; - - // updated value with inset - float mMaxShadowSize; - // actual value set by developer - float mRawMaxShadowSize; - - // multiplied value to account for shadow offset - float mShadowSize; - // actual value set by developer - float mRawShadowSize; - - private boolean mDirty = true; - - private final int mShadowStartColor; - private final int mShadowMiddleColor; - private final int mShadowEndColor; - - private boolean mAddPaddingForCorners = true; - - private float mRotation; - - /** - * If shadow size is set to a value above max shadow, we print a warning - */ - private boolean mPrintedShadowClipWarning = false; - - public ShadowDrawableWrapper(Context context, Drawable content, float radius, - float shadowSize, float maxShadowSize) { - super(content); - - mShadowStartColor = ContextCompat.getColor(context, R.color.design_fab_shadow_start_color); - mShadowMiddleColor = ContextCompat.getColor(context, R.color.design_fab_shadow_mid_color); - mShadowEndColor = ContextCompat.getColor(context, R.color.design_fab_shadow_end_color); - - mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); - mCornerShadowPaint.setStyle(Paint.Style.FILL); - mCornerRadius = Math.round(radius); - mContentBounds = new RectF(); - mEdgeShadowPaint = new Paint(mCornerShadowPaint); - mEdgeShadowPaint.setAntiAlias(false); - setShadowSize(shadowSize, maxShadowSize); - } - - /** - * Casts the value to an even integer. - */ - private static int toEven(float value) { - int i = Math.round(value); - return (i % 2 == 1) ? i - 1 : i; - } - - public void setAddPaddingForCorners(boolean addPaddingForCorners) { - mAddPaddingForCorners = addPaddingForCorners; - invalidateSelf(); - } - - @Override - public void setAlpha(int alpha) { - super.setAlpha(alpha); - mCornerShadowPaint.setAlpha(alpha); - mEdgeShadowPaint.setAlpha(alpha); - } - - @Override - protected void onBoundsChange(Rect bounds) { - mDirty = true; - } - - void setShadowSize(float shadowSize, float maxShadowSize) { - if (shadowSize < 0 || maxShadowSize < 0) { - throw new IllegalArgumentException("invalid shadow size"); - } - shadowSize = toEven(shadowSize); - maxShadowSize = toEven(maxShadowSize); - if (shadowSize > maxShadowSize) { - shadowSize = maxShadowSize; - if (!mPrintedShadowClipWarning) { - mPrintedShadowClipWarning = true; - } - } - if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) { - return; - } - mRawShadowSize = shadowSize; - mRawMaxShadowSize = maxShadowSize; - mShadowSize = Math.round(shadowSize * SHADOW_MULTIPLIER); - mMaxShadowSize = maxShadowSize; - mDirty = true; - invalidateSelf(); - } - - @Override - public boolean getPadding(Rect padding) { - int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius, - mAddPaddingForCorners)); - int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius, - mAddPaddingForCorners)); - padding.set(hOffset, vOffset, hOffset, vOffset); - return true; - } - - public static float calculateVerticalPadding(float maxShadowSize, float cornerRadius, - boolean addPaddingForCorners) { - if (addPaddingForCorners) { - return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius); - } else { - return maxShadowSize * SHADOW_MULTIPLIER; - } - } - - public static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius, - boolean addPaddingForCorners) { - if (addPaddingForCorners) { - return (float) (maxShadowSize + (1 - COS_45) * cornerRadius); - } else { - return maxShadowSize; - } - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - public void setCornerRadius(float radius) { - radius = Math.round(radius); - if (mCornerRadius == radius) { - return; - } - mCornerRadius = radius; - mDirty = true; - invalidateSelf(); - } - - @Override - public void draw(Canvas canvas) { - if (mDirty) { - buildComponents(getBounds()); - mDirty = false; - } - drawShadow(canvas); - - super.draw(canvas); - } - - final void setRotation(float rotation) { - if (mRotation != rotation) { - mRotation = rotation; - invalidateSelf(); - } - } - - private void drawShadow(Canvas canvas) { - final int rotateSaved = canvas.save(); - canvas.rotate(mRotation, mContentBounds.centerX(), mContentBounds.centerY()); - - final float edgeShadowTop = -mCornerRadius - mShadowSize; - final float shadowOffset = mCornerRadius; - final boolean drawHorizontalEdges = mContentBounds.width() - 2 * shadowOffset > 0; - final boolean drawVerticalEdges = mContentBounds.height() - 2 * shadowOffset > 0; - - final float shadowOffsetTop = mRawShadowSize - (mRawShadowSize * SHADOW_TOP_SCALE); - final float shadowOffsetHorizontal = mRawShadowSize - (mRawShadowSize * SHADOW_HORIZ_SCALE); - final float shadowOffsetBottom = mRawShadowSize - (mRawShadowSize * SHADOW_BOTTOM_SCALE); - - final float shadowScaleHorizontal = shadowOffset / (shadowOffset + shadowOffsetHorizontal); - final float shadowScaleTop = shadowOffset / (shadowOffset + shadowOffsetTop); - final float shadowScaleBottom = shadowOffset / (shadowOffset + shadowOffsetBottom); - - // LT - int saved = canvas.save(); - canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.top + shadowOffset); - canvas.scale(shadowScaleHorizontal, shadowScaleTop); - canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); - if (drawHorizontalEdges) { - // TE - canvas.scale(1f / shadowScaleHorizontal, 1f); - canvas.drawRect(0, edgeShadowTop, - mContentBounds.width() - 2 * shadowOffset, -mCornerRadius, - mEdgeShadowPaint); - } - canvas.restoreToCount(saved); - // RB - saved = canvas.save(); - canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.bottom - shadowOffset); - canvas.scale(shadowScaleHorizontal, shadowScaleBottom); - canvas.rotate(180f); - canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); - if (drawHorizontalEdges) { - // BE - canvas.scale(1f / shadowScaleHorizontal, 1f); - canvas.drawRect(0, edgeShadowTop, - mContentBounds.width() - 2 * shadowOffset, -mCornerRadius + mShadowSize, - mEdgeShadowPaint); - } - canvas.restoreToCount(saved); - // LB - saved = canvas.save(); - canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.bottom - shadowOffset); - canvas.scale(shadowScaleHorizontal, shadowScaleBottom); - canvas.rotate(270f); - canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); - if (drawVerticalEdges) { - // LE - canvas.scale(1f / shadowScaleBottom, 1f); - canvas.drawRect(0, edgeShadowTop, - mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint); - } - canvas.restoreToCount(saved); - // RT - saved = canvas.save(); - canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.top + shadowOffset); - canvas.scale(shadowScaleHorizontal, shadowScaleTop); - canvas.rotate(90f); - canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); - if (drawVerticalEdges) { - // RE - canvas.scale(1f / shadowScaleTop, 1f); - canvas.drawRect(0, edgeShadowTop, - mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint); - } - canvas.restoreToCount(saved); - - canvas.restoreToCount(rotateSaved); - } - - private void buildShadowCorners() { - RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius); - RectF outerBounds = new RectF(innerBounds); - outerBounds.inset(-mShadowSize, -mShadowSize); - - if (mCornerShadowPath == null) { - mCornerShadowPath = new Path(); - } else { - mCornerShadowPath.reset(); - } - mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD); - mCornerShadowPath.moveTo(-mCornerRadius, 0); - mCornerShadowPath.rLineTo(-mShadowSize, 0); - // outer arc - mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false); - // inner arc - mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false); - mCornerShadowPath.close(); - - float shadowRadius = -outerBounds.top; - if (shadowRadius > 0f) { - float startRatio = mCornerRadius / shadowRadius; - float midRatio = startRatio + ((1f - startRatio) / 2f); - mCornerShadowPaint.setShader(new RadialGradient(0, 0, shadowRadius, - new int[]{0, mShadowStartColor, mShadowMiddleColor, mShadowEndColor}, - new float[]{0f, startRatio, midRatio, 1f}, - Shader.TileMode.CLAMP)); - } - - // we offset the content shadowSize/2 pixels up to make it more realistic. - // this is why edge shadow shader has some extra space - // When drawing bottom edge shadow, we use that extra space. - mEdgeShadowPaint.setShader(new LinearGradient(0, innerBounds.top, 0, outerBounds.top, - new int[]{mShadowStartColor, mShadowMiddleColor, mShadowEndColor}, - new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP)); - mEdgeShadowPaint.setAntiAlias(false); - } - - private void buildComponents(Rect bounds) { - // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift. - // We could have different top-bottom offsets to avoid extra gap above but in that case - // center aligning Views inside the CardView would be problematic. - final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER; - mContentBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset, - bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset); - - getWrappedDrawable().setBounds((int) mContentBounds.left, (int) mContentBounds.top, - (int) mContentBounds.right, (int) mContentBounds.bottom); - - buildShadowCorners(); - } - - public float getCornerRadius() { - return mCornerRadius; - } - - public void setShadowSize(float size) { - setShadowSize(size, mRawMaxShadowSize); - } - - public void setMaxShadowSize(float size) { - setShadowSize(mRawShadowSize, size); - } - - public float getShadowSize() { - return mRawShadowSize; - } - - public float getMaxShadowSize() { - return mRawMaxShadowSize; - } - - public float getMinWidth() { - final float content = 2 * - Math.max(mRawMaxShadowSize, mCornerRadius + mRawMaxShadowSize / 2); - return content + mRawMaxShadowSize * 2; - } - - public float getMinHeight() { - final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius - + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2); - return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER) * 2; - } -} diff --git a/android/support/design/widget/ShadowViewDelegate.java b/android/support/design/widget/ShadowViewDelegate.java deleted file mode 100644 index 83a3a7a1..00000000 --- a/android/support/design/widget/ShadowViewDelegate.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.graphics.drawable.Drawable; - -interface ShadowViewDelegate { - float getRadius(); - void setShadowPadding(int left, int top, int right, int bottom); - void setBackgroundDrawable(Drawable background); - boolean isCompatPaddingEnabled(); -} diff --git a/android/support/design/widget/Snackbar.java b/android/support/design/widget/Snackbar.java deleted file mode 100644 index bd5ffbab..00000000 --- a/android/support/design/widget/Snackbar.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.StringRes; -import android.support.design.R; -import android.support.design.internal.SnackbarContentLayout; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.FrameLayout; -import android.widget.TextView; - -/** - * Snackbars provide lightweight feedback about an operation. They show a brief message at the - * bottom of the screen on mobile and lower left on larger devices. Snackbars appear above all other - * elements on screen and only one can be displayed at a time. - * <p> - * They automatically disappear after a timeout or after user interaction elsewhere on the screen, - * particularly after interactions that summon a new surface or activity. Snackbars can be swiped - * off screen. - * <p> - * Snackbars can contain an action which is set via - * {@link #setAction(CharSequence, android.view.View.OnClickListener)}. - * <p> - * To be notified when a snackbar has been shown or dismissed, you can provide a {@link Callback} - * via {@link BaseTransientBottomBar#addCallback(BaseCallback)}.</p> - */ -public final class Snackbar extends BaseTransientBottomBar<Snackbar> { - - /** - * Show the Snackbar indefinitely. This means that the Snackbar will be displayed from the time - * that is {@link #show() shown} until either it is dismissed, or another Snackbar is shown. - * - * @see #setDuration - */ - public static final int LENGTH_INDEFINITE = BaseTransientBottomBar.LENGTH_INDEFINITE; - - /** - * Show the Snackbar for a short period of time. - * - * @see #setDuration - */ - public static final int LENGTH_SHORT = BaseTransientBottomBar.LENGTH_SHORT; - - /** - * Show the Snackbar for a long period of time. - * - * @see #setDuration - */ - public static final int LENGTH_LONG = BaseTransientBottomBar.LENGTH_LONG; - - /** - * Callback class for {@link Snackbar} instances. - * - * Note: this class is here to provide backwards-compatible way for apps written before - * the existence of the base {@link BaseTransientBottomBar} class. - * - * @see BaseTransientBottomBar#addCallback(BaseCallback) - */ - public static class Callback extends BaseCallback<Snackbar> { - /** Indicates that the Snackbar was dismissed via a swipe.*/ - public static final int DISMISS_EVENT_SWIPE = BaseCallback.DISMISS_EVENT_SWIPE; - /** Indicates that the Snackbar was dismissed via an action click.*/ - public static final int DISMISS_EVENT_ACTION = BaseCallback.DISMISS_EVENT_ACTION; - /** Indicates that the Snackbar was dismissed via a timeout.*/ - public static final int DISMISS_EVENT_TIMEOUT = BaseCallback.DISMISS_EVENT_TIMEOUT; - /** Indicates that the Snackbar was dismissed via a call to {@link #dismiss()}.*/ - public static final int DISMISS_EVENT_MANUAL = BaseCallback.DISMISS_EVENT_MANUAL; - /** Indicates that the Snackbar was dismissed from a new Snackbar being shown.*/ - public static final int DISMISS_EVENT_CONSECUTIVE = BaseCallback.DISMISS_EVENT_CONSECUTIVE; - - @Override - public void onShown(Snackbar sb) { - // Stub implementation to make API check happy. - } - - @Override - public void onDismissed(Snackbar transientBottomBar, @DismissEvent int event) { - // Stub implementation to make API check happy. - } - } - - @Nullable private BaseCallback<Snackbar> mCallback; - - private Snackbar(ViewGroup parent, View content, ContentViewCallback contentViewCallback) { - super(parent, content, contentViewCallback); - } - - /** - * Make a Snackbar to display a message - * - * <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given - * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, - * which is defined as a {@link CoordinatorLayout} or the window decor's content view, - * whichever comes first. - * - * <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable - * certain features, such as swipe-to-dismiss and automatically moving of widgets like - * {@link FloatingActionButton}. - * - * @param view The view to find a parent from. - * @param text The text to show. Can be formatted text. - * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link - * #LENGTH_LONG} - */ - @NonNull - public static Snackbar make(@NonNull View view, @NonNull CharSequence text, - @Duration int duration) { - final ViewGroup parent = findSuitableParent(view); - if (parent == null) { - throw new IllegalArgumentException("No suitable parent found from the given view. " - + "Please provide a valid view."); - } - - final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - final SnackbarContentLayout content = - (SnackbarContentLayout) inflater.inflate( - R.layout.design_layout_snackbar_include, parent, false); - final Snackbar snackbar = new Snackbar(parent, content, content); - snackbar.setText(text); - snackbar.setDuration(duration); - return snackbar; - } - - /** - * Make a Snackbar to display a message. - * - * <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given - * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, - * which is defined as a {@link CoordinatorLayout} or the window decor's content view, - * whichever comes first. - * - * <p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable - * certain features, such as swipe-to-dismiss and automatically moving of widgets like - * {@link FloatingActionButton}. - * - * @param view The view to find a parent from. - * @param resId The resource id of the string resource to use. Can be formatted text. - * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link - * #LENGTH_LONG} - */ - @NonNull - public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) { - return make(view, view.getResources().getText(resId), duration); - } - - private static ViewGroup findSuitableParent(View view) { - ViewGroup fallback = null; - do { - if (view instanceof CoordinatorLayout) { - // We've found a CoordinatorLayout, use it - return (ViewGroup) view; - } else if (view instanceof FrameLayout) { - if (view.getId() == android.R.id.content) { - // If we've hit the decor content view, then we didn't find a CoL in the - // hierarchy, so use it. - return (ViewGroup) view; - } else { - // It's not the content view but we'll use it as our fallback - fallback = (ViewGroup) view; - } - } - - if (view != null) { - // Else, we will loop and crawl up the view hierarchy and try to find a parent - final ViewParent parent = view.getParent(); - view = parent instanceof View ? (View) parent : null; - } - } while (view != null); - - // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback - return fallback; - } - - /** - * Update the text in this {@link Snackbar}. - * - * @param message The new text for this {@link BaseTransientBottomBar}. - */ - @NonNull - public Snackbar setText(@NonNull CharSequence message) { - final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0); - final TextView tv = contentLayout.getMessageView(); - tv.setText(message); - return this; - } - - /** - * Update the text in this {@link Snackbar}. - * - * @param resId The new text for this {@link BaseTransientBottomBar}. - */ - @NonNull - public Snackbar setText(@StringRes int resId) { - return setText(getContext().getText(resId)); - } - - /** - * Set the action to be displayed in this {@link BaseTransientBottomBar}. - * - * @param resId String resource to display for the action - * @param listener callback to be invoked when the action is clicked - */ - @NonNull - public Snackbar setAction(@StringRes int resId, View.OnClickListener listener) { - return setAction(getContext().getText(resId), listener); - } - - /** - * Set the action to be displayed in this {@link BaseTransientBottomBar}. - * - * @param text Text to display for the action - * @param listener callback to be invoked when the action is clicked - */ - @NonNull - public Snackbar setAction(CharSequence text, final View.OnClickListener listener) { - final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0); - final TextView tv = contentLayout.getActionView(); - - if (TextUtils.isEmpty(text) || listener == null) { - tv.setVisibility(View.GONE); - tv.setOnClickListener(null); - } else { - tv.setVisibility(View.VISIBLE); - tv.setText(text); - tv.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - listener.onClick(view); - // Now dismiss the Snackbar - dispatchDismiss(BaseCallback.DISMISS_EVENT_ACTION); - } - }); - } - return this; - } - - /** - * Sets the text color of the action specified in - * {@link #setAction(CharSequence, View.OnClickListener)}. - */ - @NonNull - public Snackbar setActionTextColor(ColorStateList colors) { - final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0); - final TextView tv = contentLayout.getActionView(); - tv.setTextColor(colors); - return this; - } - - /** - * Sets the text color of the action specified in - * {@link #setAction(CharSequence, View.OnClickListener)}. - */ - @NonNull - public Snackbar setActionTextColor(@ColorInt int color) { - final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0); - final TextView tv = contentLayout.getActionView(); - tv.setTextColor(color); - return this; - } - - /** - * Set a callback to be called when this the visibility of this {@link Snackbar} - * changes. Note that this method is deprecated - * and you should use {@link #addCallback(BaseCallback)} to add a callback and - * {@link #removeCallback(BaseCallback)} to remove a registered callback. - * - * @param callback Callback to notify when transient bottom bar events occur. - * @deprecated Use {@link #addCallback(BaseCallback)} - * @see Callback - * @see #addCallback(BaseCallback) - * @see #removeCallback(BaseCallback) - */ - @Deprecated - @NonNull - public Snackbar setCallback(Callback callback) { - // The logic in this method emulates what we had before support for multiple - // registered callbacks. - if (mCallback != null) { - removeCallback(mCallback); - } - if (callback != null) { - addCallback(callback); - } - // Update the deprecated field so that we can remove the passed callback the next - // time we're called - mCallback = callback; - return this; - } - - /** - * @hide - * - * Note: this class is here to provide backwards-compatible way for apps written before - * the existence of the base {@link BaseTransientBottomBar} class. - */ - @RestrictTo(LIBRARY_GROUP) - public static final class SnackbarLayout extends BaseTransientBottomBar.SnackbarBaseLayout { - public SnackbarLayout(Context context) { - super(context); - } - - public SnackbarLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // Work around our backwards-compatible refactoring of Snackbar and inner content - // being inflated against snackbar's parent (instead of against the snackbar itself). - // Every child that is width=MATCH_PARENT is remeasured again and given the full width - // minus the paddings. - int childCount = getChildCount(); - int availableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child.getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) { - child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), - MeasureSpec.EXACTLY)); - } - } - } - } -} - diff --git a/android/support/design/widget/SnackbarManager.java b/android/support/design/widget/SnackbarManager.java deleted file mode 100644 index 43892d36..00000000 --- a/android/support/design/widget/SnackbarManager.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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.Message; - -import java.lang.ref.WeakReference; - -/** - * Manages {@link Snackbar}s. - */ -class SnackbarManager { - - static final int MSG_TIMEOUT = 0; - - private static final int SHORT_DURATION_MS = 1500; - private static final int LONG_DURATION_MS = 2750; - - private static SnackbarManager sSnackbarManager; - - static SnackbarManager getInstance() { - if (sSnackbarManager == null) { - sSnackbarManager = new SnackbarManager(); - } - return sSnackbarManager; - } - - private final Object mLock; - private final Handler mHandler; - - private SnackbarRecord mCurrentSnackbar; - private SnackbarRecord mNextSnackbar; - - private SnackbarManager() { - mLock = new Object(); - mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case MSG_TIMEOUT: - handleTimeout((SnackbarRecord) message.obj); - return true; - } - return false; - } - }); - } - - interface Callback { - void show(); - void dismiss(int event); - } - - public void show(int duration, Callback callback) { - synchronized (mLock) { - if (isCurrentSnackbarLocked(callback)) { - // Means that the callback is already in the queue. We'll just update the duration - mCurrentSnackbar.duration = duration; - - // If this is the Snackbar currently being shown, call re-schedule it's - // timeout - mHandler.removeCallbacksAndMessages(mCurrentSnackbar); - scheduleTimeoutLocked(mCurrentSnackbar); - return; - } else if (isNextSnackbarLocked(callback)) { - // We'll just update the duration - mNextSnackbar.duration = duration; - } else { - // Else, we need to create a new record and queue it - mNextSnackbar = new SnackbarRecord(duration, callback); - } - - if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar, - Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) { - // If we currently have a Snackbar, try and cancel it and wait in line - return; - } else { - // Clear out the current snackbar - mCurrentSnackbar = null; - // Otherwise, just show it now - showNextSnackbarLocked(); - } - } - } - - public void dismiss(Callback callback, int event) { - synchronized (mLock) { - if (isCurrentSnackbarLocked(callback)) { - cancelSnackbarLocked(mCurrentSnackbar, event); - } else if (isNextSnackbarLocked(callback)) { - cancelSnackbarLocked(mNextSnackbar, event); - } - } - } - - /** - * Should be called when a Snackbar is no longer displayed. This is after any exit - * animation has finished. - */ - public void onDismissed(Callback callback) { - synchronized (mLock) { - if (isCurrentSnackbarLocked(callback)) { - // If the callback is from a Snackbar currently show, remove it and show a new one - mCurrentSnackbar = null; - if (mNextSnackbar != null) { - showNextSnackbarLocked(); - } - } - } - } - - /** - * Should be called when a Snackbar is being shown. This is after any entrance animation has - * finished. - */ - public void onShown(Callback callback) { - synchronized (mLock) { - if (isCurrentSnackbarLocked(callback)) { - scheduleTimeoutLocked(mCurrentSnackbar); - } - } - } - - public void pauseTimeout(Callback callback) { - synchronized (mLock) { - if (isCurrentSnackbarLocked(callback) && !mCurrentSnackbar.paused) { - mCurrentSnackbar.paused = true; - mHandler.removeCallbacksAndMessages(mCurrentSnackbar); - } - } - } - - public void restoreTimeoutIfPaused(Callback callback) { - synchronized (mLock) { - if (isCurrentSnackbarLocked(callback) && mCurrentSnackbar.paused) { - mCurrentSnackbar.paused = false; - scheduleTimeoutLocked(mCurrentSnackbar); - } - } - } - - public boolean isCurrent(Callback callback) { - synchronized (mLock) { - return isCurrentSnackbarLocked(callback); - } - } - - public boolean isCurrentOrNext(Callback callback) { - synchronized (mLock) { - return isCurrentSnackbarLocked(callback) || isNextSnackbarLocked(callback); - } - } - - private static class SnackbarRecord { - final WeakReference<Callback> callback; - int duration; - boolean paused; - - SnackbarRecord(int duration, Callback callback) { - this.callback = new WeakReference<>(callback); - this.duration = duration; - } - - boolean isSnackbar(Callback callback) { - return callback != null && this.callback.get() == callback; - } - } - - private void showNextSnackbarLocked() { - if (mNextSnackbar != null) { - mCurrentSnackbar = mNextSnackbar; - mNextSnackbar = null; - - final Callback callback = mCurrentSnackbar.callback.get(); - if (callback != null) { - callback.show(); - } else { - // The callback doesn't exist any more, clear out the Snackbar - mCurrentSnackbar = null; - } - } - } - - private boolean cancelSnackbarLocked(SnackbarRecord record, int event) { - final Callback callback = record.callback.get(); - if (callback != null) { - // Make sure we remove any timeouts for the SnackbarRecord - mHandler.removeCallbacksAndMessages(record); - callback.dismiss(event); - return true; - } - return false; - } - - private boolean isCurrentSnackbarLocked(Callback callback) { - return mCurrentSnackbar != null && mCurrentSnackbar.isSnackbar(callback); - } - - private boolean isNextSnackbarLocked(Callback callback) { - return mNextSnackbar != null && mNextSnackbar.isSnackbar(callback); - } - - private void scheduleTimeoutLocked(SnackbarRecord r) { - if (r.duration == Snackbar.LENGTH_INDEFINITE) { - // If we're set to indefinite, we don't want to set a timeout - return; - } - - int durationMs = LONG_DURATION_MS; - if (r.duration > 0) { - durationMs = r.duration; - } else if (r.duration == Snackbar.LENGTH_SHORT) { - durationMs = SHORT_DURATION_MS; - } - mHandler.removeCallbacksAndMessages(r); - mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs); - } - - void handleTimeout(SnackbarRecord record) { - synchronized (mLock) { - if (mCurrentSnackbar == record || mNextSnackbar == record) { - cancelSnackbarLocked(record, Snackbar.Callback.DISMISS_EVENT_TIMEOUT); - } - } - } - -} diff --git a/android/support/design/widget/StateListAnimator.java b/android/support/design/widget/StateListAnimator.java deleted file mode 100644 index aef24be3..00000000 --- a/android/support/design/widget/StateListAnimator.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.util.StateSet; - -import java.util.ArrayList; - -final class StateListAnimator { - - private final ArrayList<Tuple> mTuples = new ArrayList<>(); - - private Tuple mLastMatch = null; - ValueAnimator mRunningAnimator = null; - - private final ValueAnimator.AnimatorListener mAnimationListener = - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - if (mRunningAnimator == animator) { - mRunningAnimator = null; - } - } - }; - - /** - * Associates the given Animation with the provided drawable state specs so that it will be run - * when the View's drawable state matches the specs. - * - * @param specs The drawable state specs to match against - * @param animator The animator to run when the specs match - */ - public void addState(int[] specs, ValueAnimator animator) { - Tuple tuple = new Tuple(specs, animator); - animator.addListener(mAnimationListener); - mTuples.add(tuple); - } - - /** - * Called by View - */ - void setState(int[] state) { - Tuple match = null; - final int count = mTuples.size(); - for (int i = 0; i < count; i++) { - final Tuple tuple = mTuples.get(i); - if (StateSet.stateSetMatches(tuple.mSpecs, state)) { - match = tuple; - break; - } - } - if (match == mLastMatch) { - return; - } - if (mLastMatch != null) { - cancel(); - } - - mLastMatch = match; - - if (match != null) { - start(match); - } - } - - private void start(Tuple match) { - mRunningAnimator = match.mAnimator; - mRunningAnimator.start(); - } - - private void cancel() { - if (mRunningAnimator != null) { - mRunningAnimator.cancel(); - mRunningAnimator = null; - } - } - - /** - * If there is an animation running for a recent state change, ends it. - * - * <p>This causes the animation to assign the end value(s) to the View.</p> - */ - public void jumpToCurrentState() { - if (mRunningAnimator != null) { - mRunningAnimator.end(); - mRunningAnimator = null; - } - } - - static class Tuple { - final int[] mSpecs; - final ValueAnimator mAnimator; - - Tuple(int[] specs, ValueAnimator animator) { - mSpecs = specs; - mAnimator = animator; - } - } -}
\ No newline at end of file diff --git a/android/support/design/widget/SwipeDismissBehavior.java b/android/support/design/widget/SwipeDismissBehavior.java deleted file mode 100644 index d8573340..00000000 --- a/android/support/design/widget/SwipeDismissBehavior.java +++ /dev/null @@ -1,411 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ViewDragHelper; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * An interaction behavior plugin for child views of {@link CoordinatorLayout} to provide support - * for the 'swipe-to-dismiss' gesture. - */ -public class SwipeDismissBehavior<V extends View> extends CoordinatorLayout.Behavior<V> { - - /** - * A view is not currently being dragged or animating as a result of a fling/snap. - */ - public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; - - /** - * A view is currently being dragged. The position is currently changing as a result - * of user input or simulated user input. - */ - public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; - - /** - * A view is currently settling into place as a result of a fling or - * predefined non-interactive motion. - */ - public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; - - /** @hide */ - @RestrictTo(LIBRARY_GROUP) - @IntDef({SWIPE_DIRECTION_START_TO_END, SWIPE_DIRECTION_END_TO_START, SWIPE_DIRECTION_ANY}) - @Retention(RetentionPolicy.SOURCE) - private @interface SwipeDirection {} - - /** - * Swipe direction that only allows swiping in the direction of start-to-end. That is - * left-to-right in LTR, or right-to-left in RTL. - */ - public static final int SWIPE_DIRECTION_START_TO_END = 0; - - /** - * Swipe direction that only allows swiping in the direction of end-to-start. That is - * right-to-left in LTR or left-to-right in RTL. - */ - public static final int SWIPE_DIRECTION_END_TO_START = 1; - - /** - * Swipe direction which allows swiping in either direction. - */ - public static final int SWIPE_DIRECTION_ANY = 2; - - private static final float DEFAULT_DRAG_DISMISS_THRESHOLD = 0.5f; - private static final float DEFAULT_ALPHA_START_DISTANCE = 0f; - private static final float DEFAULT_ALPHA_END_DISTANCE = DEFAULT_DRAG_DISMISS_THRESHOLD; - - ViewDragHelper mViewDragHelper; - OnDismissListener mListener; - private boolean mInterceptingEvents; - - private float mSensitivity = 0f; - private boolean mSensitivitySet; - - int mSwipeDirection = SWIPE_DIRECTION_ANY; - float mDragDismissThreshold = DEFAULT_DRAG_DISMISS_THRESHOLD; - float mAlphaStartSwipeDistance = DEFAULT_ALPHA_START_DISTANCE; - float mAlphaEndSwipeDistance = DEFAULT_ALPHA_END_DISTANCE; - - /** - * Callback interface used to notify the application that the view has been dismissed. - */ - public interface OnDismissListener { - /** - * Called when {@code view} has been dismissed via swiping. - */ - public void onDismiss(View view); - - /** - * Called when the drag state has changed. - * - * @param state the new state. One of - * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - */ - public void onDragStateChanged(int state); - } - - /** - * Set the listener to be used when a dismiss event occurs. - * - * @param listener the listener to use. - */ - public void setListener(OnDismissListener listener) { - mListener = listener; - } - - /** - * Sets the swipe direction for this behavior. - * - * @param direction one of the {@link #SWIPE_DIRECTION_START_TO_END}, - * {@link #SWIPE_DIRECTION_END_TO_START} or {@link #SWIPE_DIRECTION_ANY} - */ - public void setSwipeDirection(@SwipeDirection int direction) { - mSwipeDirection = direction; - } - - /** - * Set the threshold for telling if a view has been dragged enough to be dismissed. - * - * @param distance a ratio of a view's width, values are clamped to 0 >= x <= 1f; - */ - public void setDragDismissDistance(float distance) { - mDragDismissThreshold = clamp(0f, distance, 1f); - } - - /** - * The minimum swipe distance before the view's alpha is modified. - * - * @param fraction the distance as a fraction of the view's width. - */ - public void setStartAlphaSwipeDistance(float fraction) { - mAlphaStartSwipeDistance = clamp(0f, fraction, 1f); - } - - /** - * The maximum swipe distance for the view's alpha is modified. - * - * @param fraction the distance as a fraction of the view's width. - */ - public void setEndAlphaSwipeDistance(float fraction) { - mAlphaEndSwipeDistance = clamp(0f, fraction, 1f); - } - - /** - * Set the sensitivity used for detecting the start of a swipe. This only takes effect if - * no touch handling has occured yet. - * - * @param sensitivity Multiplier for how sensitive we should be about detecting - * the start of a drag. Larger values are more sensitive. 1.0f is normal. - */ - public void setSensitivity(float sensitivity) { - mSensitivity = sensitivity; - mSensitivitySet = true; - } - - @Override - public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { - boolean dispatchEventToHelper = mInterceptingEvents; - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mInterceptingEvents = parent.isPointInChildBounds(child, - (int) event.getX(), (int) event.getY()); - dispatchEventToHelper = mInterceptingEvents; - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - // Reset the ignore flag for next time - mInterceptingEvents = false; - break; - } - - if (dispatchEventToHelper) { - ensureViewDragHelper(parent); - return mViewDragHelper.shouldInterceptTouchEvent(event); - } - return false; - } - - @Override - public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { - if (mViewDragHelper != null) { - mViewDragHelper.processTouchEvent(event); - return true; - } - return false; - } - - /** - * Called when the user's input indicates that they want to swipe the given view. - * - * @param view View the user is attempting to swipe - * @return true if the view can be dismissed via swiping, false otherwise - */ - public boolean canSwipeDismissView(@NonNull View view) { - return true; - } - - private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() { - private static final int INVALID_POINTER_ID = -1; - - private int mOriginalCapturedViewLeft; - private int mActivePointerId = INVALID_POINTER_ID; - - @Override - public boolean tryCaptureView(View child, int pointerId) { - // Only capture if we don't already have an active pointer id - return mActivePointerId == INVALID_POINTER_ID && canSwipeDismissView(child); - } - - @Override - public void onViewCaptured(View capturedChild, int activePointerId) { - mActivePointerId = activePointerId; - mOriginalCapturedViewLeft = capturedChild.getLeft(); - - // The view has been captured, and thus a drag is about to start so stop any parents - // intercepting - final ViewParent parent = capturedChild.getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } - - @Override - public void onViewDragStateChanged(int state) { - if (mListener != null) { - mListener.onDragStateChanged(state); - } - } - - @Override - public void onViewReleased(View child, float xvel, float yvel) { - // Reset the active pointer ID - mActivePointerId = INVALID_POINTER_ID; - - final int childWidth = child.getWidth(); - int targetLeft; - boolean dismiss = false; - - if (shouldDismiss(child, xvel)) { - targetLeft = child.getLeft() < mOriginalCapturedViewLeft - ? mOriginalCapturedViewLeft - childWidth - : mOriginalCapturedViewLeft + childWidth; - dismiss = true; - } else { - // Else, reset back to the original left - targetLeft = mOriginalCapturedViewLeft; - } - - if (mViewDragHelper.settleCapturedViewAt(targetLeft, child.getTop())) { - ViewCompat.postOnAnimation(child, new SettleRunnable(child, dismiss)); - } else if (dismiss && mListener != null) { - mListener.onDismiss(child); - } - } - - private boolean shouldDismiss(View child, float xvel) { - if (xvel != 0f) { - final boolean isRtl = ViewCompat.getLayoutDirection(child) - == ViewCompat.LAYOUT_DIRECTION_RTL; - - if (mSwipeDirection == SWIPE_DIRECTION_ANY) { - // We don't care about the direction so return true - return true; - } else if (mSwipeDirection == SWIPE_DIRECTION_START_TO_END) { - // We only allow start-to-end swiping, so the fling needs to be in the - // correct direction - return isRtl ? xvel < 0f : xvel > 0f; - } else if (mSwipeDirection == SWIPE_DIRECTION_END_TO_START) { - // We only allow end-to-start swiping, so the fling needs to be in the - // correct direction - return isRtl ? xvel > 0f : xvel < 0f; - } - } else { - final int distance = child.getLeft() - mOriginalCapturedViewLeft; - final int thresholdDistance = Math.round(child.getWidth() * mDragDismissThreshold); - return Math.abs(distance) >= thresholdDistance; - } - - return false; - } - - @Override - public int getViewHorizontalDragRange(View child) { - return child.getWidth(); - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - final boolean isRtl = ViewCompat.getLayoutDirection(child) - == ViewCompat.LAYOUT_DIRECTION_RTL; - int min, max; - - if (mSwipeDirection == SWIPE_DIRECTION_START_TO_END) { - if (isRtl) { - min = mOriginalCapturedViewLeft - child.getWidth(); - max = mOriginalCapturedViewLeft; - } else { - min = mOriginalCapturedViewLeft; - max = mOriginalCapturedViewLeft + child.getWidth(); - } - } else if (mSwipeDirection == SWIPE_DIRECTION_END_TO_START) { - if (isRtl) { - min = mOriginalCapturedViewLeft; - max = mOriginalCapturedViewLeft + child.getWidth(); - } else { - min = mOriginalCapturedViewLeft - child.getWidth(); - max = mOriginalCapturedViewLeft; - } - } else { - min = mOriginalCapturedViewLeft - child.getWidth(); - max = mOriginalCapturedViewLeft + child.getWidth(); - } - - return clamp(min, left, max); - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - return child.getTop(); - } - - @Override - public void onViewPositionChanged(View child, int left, int top, int dx, int dy) { - final float startAlphaDistance = mOriginalCapturedViewLeft - + child.getWidth() * mAlphaStartSwipeDistance; - final float endAlphaDistance = mOriginalCapturedViewLeft - + child.getWidth() * mAlphaEndSwipeDistance; - - if (left <= startAlphaDistance) { - child.setAlpha(1f); - } else if (left >= endAlphaDistance) { - child.setAlpha(0f); - } else { - // We're between the start and end distances - final float distance = fraction(startAlphaDistance, endAlphaDistance, left); - child.setAlpha(clamp(0f, 1f - distance, 1f)); - } - } - }; - - private void ensureViewDragHelper(ViewGroup parent) { - if (mViewDragHelper == null) { - mViewDragHelper = mSensitivitySet - ? ViewDragHelper.create(parent, mSensitivity, mDragCallback) - : ViewDragHelper.create(parent, mDragCallback); - } - } - - private class SettleRunnable implements Runnable { - private final View mView; - private final boolean mDismiss; - - SettleRunnable(View view, boolean dismiss) { - mView = view; - mDismiss = dismiss; - } - - @Override - public void run() { - if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) { - ViewCompat.postOnAnimation(mView, this); - } else { - if (mDismiss && mListener != null) { - mListener.onDismiss(mView); - } - } - } - } - - static float clamp(float min, float value, float max) { - return Math.min(Math.max(min, value), max); - } - - static int clamp(int min, int value, int max) { - return Math.min(Math.max(min, value), max); - } - - /** - * Retrieve the current drag state of this behavior. This will return one of - * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - * - * @return The current drag state - */ - public int getDragState() { - return mViewDragHelper != null ? mViewDragHelper.getViewDragState() : STATE_IDLE; - } - - /** - * The fraction that {@code value} is between {@code startValue} and {@code endValue}. - */ - static float fraction(float startValue, float endValue, float value) { - return (value - startValue) / (endValue - startValue); - } -}
\ No newline at end of file diff --git a/android/support/design/widget/TabItem.java b/android/support/design/widget/TabItem.java deleted file mode 100644 index 09b01dbe..00000000 --- a/android/support/design/widget/TabItem.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2016 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.content.Context; -import android.graphics.drawable.Drawable; -import android.support.design.R; -import android.support.v7.widget.TintTypedArray; -import android.util.AttributeSet; -import android.view.View; - -/** - * TabItem is a special 'view' which allows you to declare tab items for a {@link TabLayout} - * within a layout. This view is not actually added to TabLayout, it is just a dummy which allows - * setting of a tab items's text, icon and custom layout. See TabLayout for more information on how - * to use it. - * - * @attr ref android.support.design.R.styleable#TabItem_android_icon - * @attr ref android.support.design.R.styleable#TabItem_android_text - * @attr ref android.support.design.R.styleable#TabItem_android_layout - * - * @see TabLayout - */ -public final class TabItem extends View { - final CharSequence mText; - final Drawable mIcon; - final int mCustomLayout; - - public TabItem(Context context) { - this(context, null); - } - - public TabItem(Context context, AttributeSet attrs) { - super(context, attrs); - - final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, - R.styleable.TabItem); - mText = a.getText(R.styleable.TabItem_android_text); - mIcon = a.getDrawable(R.styleable.TabItem_android_icon); - mCustomLayout = a.getResourceId(R.styleable.TabItem_android_layout, 0); - a.recycle(); - } -}
\ No newline at end of file diff --git a/android/support/design/widget/TabLayout.java b/android/support/design/widget/TabLayout.java deleted file mode 100644 index 9b81465a..00000000 --- a/android/support/design/widget/TabLayout.java +++ /dev/null @@ -1,2217 +0,0 @@ -/* - * 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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING; -import static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE; -import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.IntDef; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.StringRes; -import android.support.design.R; -import android.support.v4.util.Pools; -import android.support.v4.view.GravityCompat; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.PointerIconCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPager; -import android.support.v4.widget.TextViewCompat; -import android.support.v7.app.ActionBar; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.widget.TooltipCompat; -import android.text.Layout; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.SoundEffectConstants; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.HorizontalScrollView; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; - -/** - * TabLayout provides a horizontal layout to display tabs. - * - * <p>Population of the tabs to display is - * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can - * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)} - * respectively. To display the tab, you need to add it to the layout via one of the - * {@link #addTab(Tab)} methods. For example: - * <pre> - * TabLayout tabLayout = ...; - * tabLayout.addTab(tabLayout.newTab().setText("Tab 1")); - * tabLayout.addTab(tabLayout.newTab().setText("Tab 2")); - * tabLayout.addTab(tabLayout.newTab().setText("Tab 3")); - * </pre> - * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be - * notified when any tab's selection state has been changed. - * - * <p>You can also add items to TabLayout in your layout through the use of {@link TabItem}. - * An example usage is like so:</p> - * - * <pre> - * <android.support.design.widget.TabLayout - * android:layout_height="wrap_content" - * android:layout_width="match_parent"> - * - * <android.support.design.widget.TabItem - * android:text="@string/tab_text"/> - * - * <android.support.design.widget.TabItem - * android:icon="@drawable/ic_android"/> - * - * </android.support.design.widget.TabLayout> - * </pre> - * - * <h3>ViewPager integration</h3> - * <p> - * If you're using a {@link android.support.v4.view.ViewPager} together - * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together. - * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.</p> - * - * <p> - * This view also supports being used as part of a ViewPager's decor, and can be added - * directly to the ViewPager in a layout resource file like so:</p> - * - * <pre> - * <android.support.v4.view.ViewPager - * android:layout_width="match_parent" - * android:layout_height="match_parent"> - * - * <android.support.design.widget.TabLayout - * android:layout_width="match_parent" - * android:layout_height="wrap_content" - * android:layout_gravity="top" /> - * - * </android.support.v4.view.ViewPager> - * </pre> - * - * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a> - * - * @attr ref android.support.design.R.styleable#TabLayout_tabPadding - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom - * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart - * @attr ref android.support.design.R.styleable#TabLayout_tabBackground - * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance - */ -@ViewPager.DecorView -public class TabLayout extends HorizontalScrollView { - - private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps - static final int DEFAULT_GAP_TEXT_ICON = 8; // dps - private static final int INVALID_WIDTH = -1; - private static final int DEFAULT_HEIGHT = 48; // dps - private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps - static final int FIXED_WRAP_GUTTER_MIN = 16; //dps - static final int MOTION_NON_ADJACENT_OFFSET = 24; - - private static final int ANIMATION_DURATION = 300; - - private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<>(16); - - /** - * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab - * labels and a larger number of tabs. They are best used for browsing contexts in touch - * interfaces when users don’t need to directly compare the tab labels. - * - * @see #setTabMode(int) - * @see #getTabMode() - */ - public static final int MODE_SCROLLABLE = 0; - - /** - * Fixed tabs display all tabs concurrently and are best used with content that benefits from - * quick pivots between tabs. The maximum number of tabs is limited by the view’s width. - * Fixed tabs have equal width, based on the widest tab label. - * - * @see #setTabMode(int) - * @see #getTabMode() - */ - public static final int MODE_FIXED = 1; - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED}) - @Retention(RetentionPolicy.SOURCE) - public @interface Mode {} - - /** - * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect - * when used with {@link #MODE_FIXED}. - * - * @see #setTabGravity(int) - * @see #getTabGravity() - */ - public static final int GRAVITY_FILL = 0; - - /** - * Gravity used to lay out the tabs in the center of the {@link TabLayout}. - * - * @see #setTabGravity(int) - * @see #getTabGravity() - */ - public static final int GRAVITY_CENTER = 1; - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER}) - @Retention(RetentionPolicy.SOURCE) - public @interface TabGravity {} - - /** - * Callback interface invoked when a tab's selection state changes. - */ - public interface OnTabSelectedListener { - - /** - * Called when a tab enters the selected state. - * - * @param tab The tab that was selected - */ - public void onTabSelected(Tab tab); - - /** - * Called when a tab exits the selected state. - * - * @param tab The tab that was unselected - */ - public void onTabUnselected(Tab tab); - - /** - * Called when a tab that is already selected is chosen again by the user. Some applications - * may use this action to return to the top level of a category. - * - * @param tab The tab that was reselected. - */ - public void onTabReselected(Tab tab); - } - - private final ArrayList<Tab> mTabs = new ArrayList<>(); - private Tab mSelectedTab; - - private final SlidingTabStrip mTabStrip; - - int mTabPaddingStart; - int mTabPaddingTop; - int mTabPaddingEnd; - int mTabPaddingBottom; - - int mTabTextAppearance; - ColorStateList mTabTextColors; - float mTabTextSize; - float mTabTextMultiLineSize; - - final int mTabBackgroundResId; - - int mTabMaxWidth = Integer.MAX_VALUE; - private final int mRequestedTabMinWidth; - private final int mRequestedTabMaxWidth; - private final int mScrollableTabMinWidth; - - private int mContentInsetStart; - - int mTabGravity; - int mMode; - - private OnTabSelectedListener mSelectedListener; - private final ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>(); - private OnTabSelectedListener mCurrentVpSelectedListener; - - private ValueAnimator mScrollAnimator; - - ViewPager mViewPager; - private PagerAdapter mPagerAdapter; - private DataSetObserver mPagerAdapterObserver; - private TabLayoutOnPageChangeListener mPageChangeListener; - private AdapterChangeListener mAdapterChangeListener; - private boolean mSetupViewPagerImplicitly; - - // Pool we use as a simple RecyclerBin - private final Pools.Pool<TabView> mTabViewPool = new Pools.SimplePool<>(12); - - public TabLayout(Context context) { - this(context, null); - } - - public TabLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - ThemeUtils.checkAppCompatTheme(context); - - // Disable the Scroll Bar - setHorizontalScrollBarEnabled(false); - - // Add the TabStrip - mTabStrip = new SlidingTabStrip(context); - super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout, - defStyleAttr, R.style.Widget_Design_TabLayout); - - mTabStrip.setSelectedIndicatorHeight( - a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0)); - mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0)); - - mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a - .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0); - mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart, - mTabPaddingStart); - mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop, - mTabPaddingTop); - mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd, - mTabPaddingEnd); - mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom, - mTabPaddingBottom); - - mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance, - R.style.TextAppearance_Design_Tab); - - // Text colors/sizes come from the text appearance first - final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance, - android.support.v7.appcompat.R.styleable.TextAppearance); - try { - mTabTextSize = ta.getDimensionPixelSize( - android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0); - mTabTextColors = ta.getColorStateList( - android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor); - } finally { - ta.recycle(); - } - - if (a.hasValue(R.styleable.TabLayout_tabTextColor)) { - // If we have an explicit text color set, use it instead - mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor); - } - - if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) { - // We have an explicit selected text color set, so we need to make merge it with the - // current colors. This is exposed so that developers can use theme attributes to set - // this (theme attrs in ColorStateLists are Lollipop+) - final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0); - mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected); - } - - mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, - INVALID_WIDTH); - mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, - INVALID_WIDTH); - mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0); - mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0); - mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED); - mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL); - a.recycle(); - - // TODO add attr for these - final Resources res = getResources(); - mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line); - mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width); - - // Now apply the tab mode and gravity - applyModeAndGravity(); - } - - /** - * Sets the tab indicator's color for the currently selected tab. - * - * @param color color to use for the indicator - * - * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorColor - */ - public void setSelectedTabIndicatorColor(@ColorInt int color) { - mTabStrip.setSelectedIndicatorColor(color); - } - - /** - * Sets the tab indicator's height for the currently selected tab. - * - * @param height height to use for the indicator in pixels - * - * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorHeight - */ - public void setSelectedTabIndicatorHeight(int height) { - mTabStrip.setSelectedIndicatorHeight(height); - } - - /** - * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as - * part of a scrolling container such as {@link android.support.v4.view.ViewPager}. - * <p> - * Calling this method does not update the selected tab, it is only used for drawing purposes. - * - * @param position current scroll position - * @param positionOffset Value from [0, 1) indicating the offset from {@code position}. - * @param updateSelectedText Whether to update the text's selected state. - */ - public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) { - setScrollPosition(position, positionOffset, updateSelectedText, true); - } - - void setScrollPosition(int position, float positionOffset, boolean updateSelectedText, - boolean updateIndicatorPosition) { - final int roundedPosition = Math.round(position + positionOffset); - if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) { - return; - } - - // Set the indicator position, if enabled - if (updateIndicatorPosition) { - mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset); - } - - // Now update the scroll position, canceling any running animation - if (mScrollAnimator != null && mScrollAnimator.isRunning()) { - mScrollAnimator.cancel(); - } - scrollTo(calculateScrollXForTab(position, positionOffset), 0); - - // Update the 'selected state' view as we scroll, if enabled - if (updateSelectedText) { - setSelectedTabView(roundedPosition); - } - } - - private float getScrollPosition() { - return mTabStrip.getIndicatorPosition(); - } - - /** - * Add a tab to this layout. The tab will be added at the end of the list. - * If this is the first tab to be added it will become the selected tab. - * - * @param tab Tab to add - */ - public void addTab(@NonNull Tab tab) { - addTab(tab, mTabs.isEmpty()); - } - - /** - * Add a tab to this layout. The tab will be inserted at <code>position</code>. - * If this is the first tab to be added it will become the selected tab. - * - * @param tab The tab to add - * @param position The new position of the tab - */ - public void addTab(@NonNull Tab tab, int position) { - addTab(tab, position, mTabs.isEmpty()); - } - - /** - * Add a tab to this layout. The tab will be added at the end of the list. - * - * @param tab Tab to add - * @param setSelected True if the added tab should become the selected tab. - */ - public void addTab(@NonNull Tab tab, boolean setSelected) { - addTab(tab, mTabs.size(), setSelected); - } - - /** - * Add a tab to this layout. The tab will be inserted at <code>position</code>. - * - * @param tab The tab to add - * @param position The new position of the tab - * @param setSelected True if the added tab should become the selected tab. - */ - public void addTab(@NonNull Tab tab, int position, boolean setSelected) { - if (tab.mParent != this) { - throw new IllegalArgumentException("Tab belongs to a different TabLayout."); - } - configureTab(tab, position); - addTabView(tab); - - if (setSelected) { - tab.select(); - } - } - - private void addTabFromItemView(@NonNull TabItem item) { - final Tab tab = newTab(); - if (item.mText != null) { - tab.setText(item.mText); - } - if (item.mIcon != null) { - tab.setIcon(item.mIcon); - } - if (item.mCustomLayout != 0) { - tab.setCustomView(item.mCustomLayout); - } - if (!TextUtils.isEmpty(item.getContentDescription())) { - tab.setContentDescription(item.getContentDescription()); - } - addTab(tab); - } - - /** - * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and - * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}. - */ - @Deprecated - public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) { - // The logic in this method emulates what we had before support for multiple - // registered listeners. - if (mSelectedListener != null) { - removeOnTabSelectedListener(mSelectedListener); - } - // Update the deprecated field so that we can remove the passed listener the next - // time we're called - mSelectedListener = listener; - if (listener != null) { - addOnTabSelectedListener(listener); - } - } - - /** - * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection - * changes. - * - * <p>Components that add a listener should take care to remove it when finished via - * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.</p> - * - * @param listener listener to add - */ - public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - if (!mSelectedListeners.contains(listener)) { - mSelectedListeners.add(listener); - } - } - - /** - * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via - * {@link #addOnTabSelectedListener(OnTabSelectedListener)}. - * - * @param listener listener to remove - */ - public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - mSelectedListeners.remove(listener); - } - - /** - * Remove all previously added {@link TabLayout.OnTabSelectedListener}s. - */ - public void clearOnTabSelectedListeners() { - mSelectedListeners.clear(); - } - - /** - * Create and return a new {@link Tab}. You need to manually add this using - * {@link #addTab(Tab)} or a related method. - * - * @return A new Tab - * @see #addTab(Tab) - */ - @NonNull - public Tab newTab() { - Tab tab = sTabPool.acquire(); - if (tab == null) { - tab = new Tab(); - } - tab.mParent = this; - tab.mView = createTabView(tab); - return tab; - } - - /** - * Returns the number of tabs currently registered with the action bar. - * - * @return Tab count - */ - public int getTabCount() { - return mTabs.size(); - } - - /** - * Returns the tab at the specified index. - */ - @Nullable - public Tab getTabAt(int index) { - return (index < 0 || index >= getTabCount()) ? null : mTabs.get(index); - } - - /** - * Returns the position of the current selected tab. - * - * @return selected tab position, or {@code -1} if there isn't a selected tab. - */ - public int getSelectedTabPosition() { - return mSelectedTab != null ? mSelectedTab.getPosition() : -1; - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param tab The tab to remove - */ - public void removeTab(Tab tab) { - if (tab.mParent != this) { - throw new IllegalArgumentException("Tab does not belong to this TabLayout."); - } - - removeTabAt(tab.getPosition()); - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param position Position of the tab to remove - */ - public void removeTabAt(int position) { - final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; - removeTabViewAt(position); - - final Tab removedTab = mTabs.remove(position); - if (removedTab != null) { - removedTab.reset(); - sTabPool.release(removedTab); - } - - final int newTabCount = mTabs.size(); - for (int i = position; i < newTabCount; i++) { - mTabs.get(i).setPosition(i); - } - - if (selectedTabPosition == position) { - selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); - } - } - - /** - * Remove all tabs from the action bar and deselect the current tab. - */ - public void removeAllTabs() { - // Remove all the views - for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) { - removeTabViewAt(i); - } - - for (final Iterator<Tab> i = mTabs.iterator(); i.hasNext();) { - final Tab tab = i.next(); - i.remove(); - tab.reset(); - sTabPool.release(tab); - } - - mSelectedTab = null; - } - - /** - * Set the behavior mode for the Tabs in this layout. The valid input options are: - * <ul> - * <li>{@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used - * with content that benefits from quick pivots between tabs.</li> - * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment, - * and can contain longer tab labels and a larger number of tabs. They are best used for - * browsing contexts in touch interfaces when users don’t need to directly compare the tab - * labels. This mode is commonly used with a {@link android.support.v4.view.ViewPager}.</li> - * </ul> - * - * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabMode - */ - public void setTabMode(@Mode int mode) { - if (mode != mMode) { - mMode = mode; - applyModeAndGravity(); - } - } - - /** - * Returns the current mode used by this {@link TabLayout}. - * - * @see #setTabMode(int) - */ - @Mode - public int getTabMode() { - return mMode; - } - - /** - * Set the gravity to use when laying out the tabs. - * - * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabGravity - */ - public void setTabGravity(@TabGravity int gravity) { - if (mTabGravity != gravity) { - mTabGravity = gravity; - applyModeAndGravity(); - } - } - - /** - * The current gravity used for laying out tabs. - * - * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. - */ - @TabGravity - public int getTabGravity() { - return mTabGravity; - } - - /** - * Sets the text colors for the different states (normal, selected) used for the tabs. - * - * @see #getTabTextColors() - */ - public void setTabTextColors(@Nullable ColorStateList textColor) { - if (mTabTextColors != textColor) { - mTabTextColors = textColor; - updateAllTabs(); - } - } - - /** - * Gets the text colors for the different states (normal, selected) used for the tabs. - */ - @Nullable - public ColorStateList getTabTextColors() { - return mTabTextColors; - } - - /** - * Sets the text colors for the different states (normal, selected) used for the tabs. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabTextColor - * @attr ref android.support.design.R.styleable#TabLayout_tabSelectedTextColor - */ - public void setTabTextColors(int normalColor, int selectedColor) { - setTabTextColors(createColorStateList(normalColor, selectedColor)); - } - - /** - * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. - * - * <p>This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with - * auto-refresh enabled.</p> - * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - */ - public void setupWithViewPager(@Nullable ViewPager viewPager) { - setupWithViewPager(viewPager, true); - } - - /** - * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. - * - * <p>This method will link the given ViewPager and this TabLayout together so that - * changes in one are automatically reflected in the other. This includes scroll state changes - * and clicks. The tabs displayed in this layout will be populated - * from the ViewPager adapter's page titles.</p> - * - * <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will - * trigger this layout to re-populate itself from the adapter's titles.</p> - * - * <p>If the given ViewPager is non-null, it needs to already have a - * {@link PagerAdapter} set.</p> - * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's - * content changes - */ - public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) { - setupWithViewPager(viewPager, autoRefresh, false); - } - - private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh, - boolean implicitSetup) { - if (mViewPager != null) { - // If we've already been setup with a ViewPager, remove us from it - if (mPageChangeListener != null) { - mViewPager.removeOnPageChangeListener(mPageChangeListener); - } - if (mAdapterChangeListener != null) { - mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener); - } - } - - if (mCurrentVpSelectedListener != null) { - // If we already have a tab selected listener for the ViewPager, remove it - removeOnTabSelectedListener(mCurrentVpSelectedListener); - mCurrentVpSelectedListener = null; - } - - if (viewPager != null) { - mViewPager = viewPager; - - // Add our custom OnPageChangeListener to the ViewPager - if (mPageChangeListener == null) { - mPageChangeListener = new TabLayoutOnPageChangeListener(this); - } - mPageChangeListener.reset(); - viewPager.addOnPageChangeListener(mPageChangeListener); - - // Now we'll add a tab selected listener to set ViewPager's current item - mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager); - addOnTabSelectedListener(mCurrentVpSelectedListener); - - final PagerAdapter adapter = viewPager.getAdapter(); - if (adapter != null) { - // Now we'll populate ourselves from the pager adapter, adding an observer if - // autoRefresh is enabled - setPagerAdapter(adapter, autoRefresh); - } - - // Add a listener so that we're notified of any adapter changes - if (mAdapterChangeListener == null) { - mAdapterChangeListener = new AdapterChangeListener(); - } - mAdapterChangeListener.setAutoRefresh(autoRefresh); - viewPager.addOnAdapterChangeListener(mAdapterChangeListener); - - // Now update the scroll position to match the ViewPager's current item - setScrollPosition(viewPager.getCurrentItem(), 0f, true); - } else { - // We've been given a null ViewPager so we need to clear out the internal state, - // listeners and observers - mViewPager = null; - setPagerAdapter(null, false); - } - - mSetupViewPagerImplicitly = implicitSetup; - } - - /** - * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a TabLayout with a ViewPager - * together. When that method is used, the TabLayout will be automatically updated - * when the {@link PagerAdapter} is changed. - */ - @Deprecated - public void setTabsFromPagerAdapter(@Nullable final PagerAdapter adapter) { - setPagerAdapter(adapter, false); - } - - @Override - public boolean shouldDelayChildPressedState() { - // Only delay the pressed state if the tabs can scroll - return getTabScrollRange() > 0; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mViewPager == null) { - // If we don't have a ViewPager already, check if our parent is a ViewPager to - // setup with it automatically - final ViewParent vp = getParent(); - if (vp instanceof ViewPager) { - // If we have a ViewPager parent and we've been added as part of its decor, let's - // assume that we should automatically setup to display any titles - setupWithViewPager((ViewPager) vp, true, true); - } - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (mSetupViewPagerImplicitly) { - // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc - setupWithViewPager(null); - mSetupViewPagerImplicitly = false; - } - } - - private int getTabScrollRange() { - return Math.max(0, mTabStrip.getWidth() - getWidth() - getPaddingLeft() - - getPaddingRight()); - } - - void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) { - if (mPagerAdapter != null && mPagerAdapterObserver != null) { - // If we already have a PagerAdapter, unregister our observer - mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver); - } - - mPagerAdapter = adapter; - - if (addObserver && adapter != null) { - // Register our observer on the new adapter - if (mPagerAdapterObserver == null) { - mPagerAdapterObserver = new PagerAdapterObserver(); - } - adapter.registerDataSetObserver(mPagerAdapterObserver); - } - - // Finally make sure we reflect the new adapter - populateFromPagerAdapter(); - } - - void populateFromPagerAdapter() { - removeAllTabs(); - - if (mPagerAdapter != null) { - final int adapterCount = mPagerAdapter.getCount(); - for (int i = 0; i < adapterCount; i++) { - addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false); - } - - // Make sure we reflect the currently set ViewPager item - if (mViewPager != null && adapterCount > 0) { - final int curItem = mViewPager.getCurrentItem(); - if (curItem != getSelectedTabPosition() && curItem < getTabCount()) { - selectTab(getTabAt(curItem)); - } - } - } - } - - private void updateAllTabs() { - for (int i = 0, z = mTabs.size(); i < z; i++) { - mTabs.get(i).updateView(); - } - } - - private TabView createTabView(@NonNull final Tab tab) { - TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null; - if (tabView == null) { - tabView = new TabView(getContext()); - } - tabView.setTab(tab); - tabView.setFocusable(true); - tabView.setMinimumWidth(getTabMinWidth()); - return tabView; - } - - private void configureTab(Tab tab, int position) { - tab.setPosition(position); - mTabs.add(position, tab); - - final int count = mTabs.size(); - for (int i = position + 1; i < count; i++) { - mTabs.get(i).setPosition(i); - } - } - - private void addTabView(Tab tab) { - final TabView tabView = tab.mView; - mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs()); - } - - @Override - public void addView(View child) { - addViewInternal(child); - } - - @Override - public void addView(View child, int index) { - addViewInternal(child); - } - - @Override - public void addView(View child, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - private void addViewInternal(final View child) { - if (child instanceof TabItem) { - addTabFromItemView((TabItem) child); - } else { - throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout"); - } - } - - private LinearLayout.LayoutParams createLayoutParamsForTabs() { - final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - updateTabViewLayoutParams(lp); - return lp; - } - - private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) { - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) { - lp.width = 0; - lp.weight = 1; - } else { - lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; - lp.weight = 0; - } - } - - int dpToPx(int dps) { - return Math.round(getResources().getDisplayMetrics().density * dps); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // If we have a MeasureSpec which allows us to decide our height, try and use the default - // height - final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom(); - switch (MeasureSpec.getMode(heightMeasureSpec)) { - case MeasureSpec.AT_MOST: - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)), - MeasureSpec.EXACTLY); - break; - case MeasureSpec.UNSPECIFIED: - heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY); - break; - } - - final int specWidth = MeasureSpec.getSize(widthMeasureSpec); - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { - // If we don't have an unspecified width spec, use the given size to calculate - // the max tab width - mTabMaxWidth = mRequestedTabMaxWidth > 0 - ? mRequestedTabMaxWidth - : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN); - } - - // Now super measure itself using the (possibly) modified height spec - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (getChildCount() == 1) { - // If we're in fixed mode then we need to make the tab strip is the same width as us - // so we don't scroll - final View child = getChildAt(0); - boolean remeasure = false; - - switch (mMode) { - case MODE_SCROLLABLE: - // We only need to resize the child if it's smaller than us. This is similar - // to fillViewport - remeasure = child.getMeasuredWidth() < getMeasuredWidth(); - break; - case MODE_FIXED: - // Resize the child so that it doesn't scroll - remeasure = child.getMeasuredWidth() != getMeasuredWidth(); - break; - } - - if (remeasure) { - // Re-measure the child with a widthSpec set to be exactly our measure width - int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() - + getPaddingBottom(), child.getLayoutParams().height); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - getMeasuredWidth(), MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - } - } - - private void removeTabViewAt(int position) { - final TabView view = (TabView) mTabStrip.getChildAt(position); - mTabStrip.removeViewAt(position); - if (view != null) { - view.reset(); - mTabViewPool.release(view); - } - requestLayout(); - } - - private void animateToTab(int newPosition) { - if (newPosition == Tab.INVALID_POSITION) { - return; - } - - if (getWindowToken() == null || !ViewCompat.isLaidOut(this) - || mTabStrip.childrenNeedLayout()) { - // If we don't have a window token, or we haven't been laid out yet just draw the new - // position now - setScrollPosition(newPosition, 0f, true); - return; - } - - final int startScrollX = getScrollX(); - final int targetScrollX = calculateScrollXForTab(newPosition, 0); - - if (startScrollX != targetScrollX) { - ensureScrollAnimator(); - - mScrollAnimator.setIntValues(startScrollX, targetScrollX); - mScrollAnimator.start(); - } - - // Now animate the indicator - mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION); - } - - private void ensureScrollAnimator() { - if (mScrollAnimator == null) { - mScrollAnimator = new ValueAnimator(); - mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - mScrollAnimator.setDuration(ANIMATION_DURATION); - mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - scrollTo((int) animator.getAnimatedValue(), 0); - } - }); - } - } - - void setScrollAnimatorListener(Animator.AnimatorListener listener) { - ensureScrollAnimator(); - mScrollAnimator.addListener(listener); - } - - private void setSelectedTabView(int position) { - final int tabCount = mTabStrip.getChildCount(); - if (position < tabCount) { - for (int i = 0; i < tabCount; i++) { - final View child = mTabStrip.getChildAt(i); - child.setSelected(i == position); - } - } - } - - void selectTab(Tab tab) { - selectTab(tab, true); - } - - void selectTab(final Tab tab, boolean updateIndicator) { - final Tab currentTab = mSelectedTab; - - if (currentTab == tab) { - if (currentTab != null) { - dispatchTabReselected(tab); - animateToTab(tab.getPosition()); - } - } else { - final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION; - if (updateIndicator) { - if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION) - && newPosition != Tab.INVALID_POSITION) { - // If we don't currently have a tab, just draw the indicator - setScrollPosition(newPosition, 0f, true); - } else { - animateToTab(newPosition); - } - if (newPosition != Tab.INVALID_POSITION) { - setSelectedTabView(newPosition); - } - } - if (currentTab != null) { - dispatchTabUnselected(currentTab); - } - mSelectedTab = tab; - if (tab != null) { - dispatchTabSelected(tab); - } - } - } - - private void dispatchTabSelected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabSelected(tab); - } - } - - private void dispatchTabUnselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabUnselected(tab); - } - } - - private void dispatchTabReselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabReselected(tab); - } - } - - private int calculateScrollXForTab(int position, float positionOffset) { - if (mMode == MODE_SCROLLABLE) { - final View selectedChild = mTabStrip.getChildAt(position); - final View nextChild = position + 1 < mTabStrip.getChildCount() - ? mTabStrip.getChildAt(position + 1) - : null; - final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0; - final int nextWidth = nextChild != null ? nextChild.getWidth() : 0; - - // base scroll amount: places center of tab in center of parent - int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (getWidth() / 2); - // offset amount: fraction of the distance between centers of tabs - int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset); - - return (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) - ? scrollBase + scrollOffset - : scrollBase - scrollOffset; - } - return 0; - } - - private void applyModeAndGravity() { - int paddingStart = 0; - if (mMode == MODE_SCROLLABLE) { - // If we're scrollable, or fixed at start, inset using padding - paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart); - } - ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0); - - switch (mMode) { - case MODE_FIXED: - mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL); - break; - case MODE_SCROLLABLE: - mTabStrip.setGravity(GravityCompat.START); - break; - } - - updateTabViews(true); - } - - void updateTabViews(final boolean requestLayout) { - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - View child = mTabStrip.getChildAt(i); - child.setMinimumWidth(getTabMinWidth()); - updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams()); - if (requestLayout) { - child.requestLayout(); - } - } - } - - /** - * A tab in this layout. Instances can be created via {@link #newTab()}. - */ - public static final class Tab { - - /** - * An invalid position for a tab. - * - * @see #getPosition() - */ - public static final int INVALID_POSITION = -1; - - private Object mTag; - private Drawable mIcon; - private CharSequence mText; - private CharSequence mContentDesc; - private int mPosition = INVALID_POSITION; - private View mCustomView; - - TabLayout mParent; - TabView mView; - - Tab() { - // Private constructor - } - - /** - * @return This Tab's tag object. - */ - @Nullable - public Object getTag() { - return mTag; - } - - /** - * Give this Tab an arbitrary object to hold for later use. - * - * @param tag Object to store - * @return The current instance for call chaining - */ - @NonNull - public Tab setTag(@Nullable Object tag) { - mTag = tag; - return this; - } - - - /** - * Returns the custom view used for this tab. - * - * @see #setCustomView(View) - * @see #setCustomView(int) - */ - @Nullable - public View getCustomView() { - return mCustomView; - } - - /** - * Set a custom view to be used for this tab. - * <p> - * If the provided view contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - * </p> - * - * @param view Custom view to be used as a tab. - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@Nullable View view) { - mCustomView = view; - updateView(); - return this; - } - - /** - * Set a custom view to be used for this tab. - * <p> - * If the inflated layout contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - * </p> - * - * @param resId A layout resource to inflate and use as a custom tab view - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@LayoutRes int resId) { - final LayoutInflater inflater = LayoutInflater.from(mView.getContext()); - return setCustomView(inflater.inflate(resId, mView, false)); - } - - /** - * Return the icon associated with this tab. - * - * @return The tab's icon - */ - @Nullable - public Drawable getIcon() { - return mIcon; - } - - /** - * Return the current position of this tab in the action bar. - * - * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in - * the action bar. - */ - public int getPosition() { - return mPosition; - } - - void setPosition(int position) { - mPosition = position; - } - - /** - * Return the text of this tab. - * - * @return The tab's text - */ - @Nullable - public CharSequence getText() { - return mText; - } - - /** - * Set the icon displayed on this tab. - * - * @param icon The drawable to use as an icon - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@Nullable Drawable icon) { - mIcon = icon; - updateView(); - return this; - } - - /** - * Set the icon displayed on this tab. - * - * @param resId A resource ID referring to the icon that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@DrawableRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setIcon(AppCompatResources.getDrawable(mParent.getContext(), resId)); - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param text The text to display - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@Nullable CharSequence text) { - mText = text; - updateView(); - return this; - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param resId A resource ID referring to the text that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setText(mParent.getResources().getText(resId)); - } - - /** - * Select this tab. Only valid if the tab has been added to the action bar. - */ - public void select() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - mParent.selectTab(this); - } - - /** - * Returns true if this tab is currently selected. - */ - public boolean isSelected() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return mParent.getSelectedTabPosition() == mPosition; - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param resId A resource ID referring to the description text - * @return The current instance for call chaining - * @see #setContentDescription(CharSequence) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setContentDescription(mParent.getResources().getText(resId)); - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param contentDesc Description of this tab's content - * @return The current instance for call chaining - * @see #setContentDescription(int) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@Nullable CharSequence contentDesc) { - mContentDesc = contentDesc; - updateView(); - return this; - } - - /** - * Gets a brief description of this tab's content for use in accessibility support. - * - * @return Description of this tab's content - * @see #setContentDescription(CharSequence) - * @see #setContentDescription(int) - */ - @Nullable - public CharSequence getContentDescription() { - return mContentDesc; - } - - void updateView() { - if (mView != null) { - mView.update(); - } - } - - void reset() { - mParent = null; - mView = null; - mTag = null; - mIcon = null; - mText = null; - mContentDesc = null; - mPosition = INVALID_POSITION; - mCustomView = null; - } - } - - class TabView extends LinearLayout { - private Tab mTab; - private TextView mTextView; - private ImageView mIconView; - - private View mCustomView; - private TextView mCustomTextView; - private ImageView mCustomIconView; - - private int mDefaultMaxLines = 2; - - public TabView(Context context) { - super(context); - if (mTabBackgroundResId != 0) { - ViewCompat.setBackground( - this, AppCompatResources.getDrawable(context, mTabBackgroundResId)); - } - ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop, - mTabPaddingEnd, mTabPaddingBottom); - setGravity(Gravity.CENTER); - setOrientation(VERTICAL); - setClickable(true); - ViewCompat.setPointerIcon(this, - PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND)); - } - - @Override - public boolean performClick() { - final boolean handled = super.performClick(); - - if (mTab != null) { - if (!handled) { - playSoundEffect(SoundEffectConstants.CLICK); - } - mTab.select(); - return true; - } else { - return handled; - } - } - - @Override - public void setSelected(final boolean selected) { - final boolean changed = isSelected() != selected; - - super.setSelected(selected); - - if (changed && selected && Build.VERSION.SDK_INT < 16) { - // Pre-JB we need to manually send the TYPE_VIEW_SELECTED event - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - - // Always dispatch this to the child views, regardless of whether the value has - // changed - if (mTextView != null) { - mTextView.setSelected(selected); - } - if (mIconView != null) { - mIconView.setSelected(selected); - } - if (mCustomView != null) { - mCustomView.setSelected(selected); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - // This view masquerades as an action bar tab. - event.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - // This view masquerades as an action bar tab. - info.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) { - final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec); - final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec); - final int maxWidth = getTabMaxWidth(); - - final int widthMeasureSpec; - final int heightMeasureSpec = origHeightMeasureSpec; - - if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED - || specWidthSize > maxWidth)) { - // If we have a max width and a given spec which is either unspecified or - // larger than the max width, update the width spec using the same mode - widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST); - } else { - // Else, use the original width spec - widthMeasureSpec = origWidthMeasureSpec; - } - - // Now lets measure - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // We need to switch the text size based on whether the text is spanning 2 lines or not - if (mTextView != null) { - final Resources res = getResources(); - float textSize = mTabTextSize; - int maxLines = mDefaultMaxLines; - - if (mIconView != null && mIconView.getVisibility() == VISIBLE) { - // If the icon view is being displayed, we limit the text to 1 line - maxLines = 1; - } else if (mTextView != null && mTextView.getLineCount() > 1) { - // Otherwise when we have text which wraps we reduce the text size - textSize = mTabTextMultiLineSize; - } - - final float curTextSize = mTextView.getTextSize(); - final int curLineCount = mTextView.getLineCount(); - final int curMaxLines = TextViewCompat.getMaxLines(mTextView); - - if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) { - // We've got a new text size and/or max lines... - boolean updateTextView = true; - - if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) { - // If we're in fixed mode, going up in text size and currently have 1 line - // then it's very easy to get into an infinite recursion. - // To combat that we check to see if the change in text size - // will cause a line count change. If so, abort the size change and stick - // to the smaller size. - final Layout layout = mTextView.getLayout(); - if (layout == null || approximateLineWidth(layout, 0, textSize) - > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) { - updateTextView = false; - } - } - - if (updateTextView) { - mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - mTextView.setMaxLines(maxLines); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - } - - void setTab(@Nullable final Tab tab) { - if (tab != mTab) { - mTab = tab; - update(); - } - } - - void reset() { - setTab(null); - setSelected(false); - } - - final void update() { - final Tab tab = mTab; - final View custom = tab != null ? tab.getCustomView() : null; - if (custom != null) { - final ViewParent customParent = custom.getParent(); - if (customParent != this) { - if (customParent != null) { - ((ViewGroup) customParent).removeView(custom); - } - addView(custom); - } - mCustomView = custom; - if (mTextView != null) { - mTextView.setVisibility(GONE); - } - if (mIconView != null) { - mIconView.setVisibility(GONE); - mIconView.setImageDrawable(null); - } - - mCustomTextView = (TextView) custom.findViewById(android.R.id.text1); - if (mCustomTextView != null) { - mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView); - } - mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon); - } else { - // We do not have a custom view. Remove one if it already exists - if (mCustomView != null) { - removeView(mCustomView); - mCustomView = null; - } - mCustomTextView = null; - mCustomIconView = null; - } - - if (mCustomView == null) { - // If there isn't a custom view, we'll us our own in-built layouts - if (mIconView == null) { - ImageView iconView = (ImageView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_icon, this, false); - addView(iconView, 0); - mIconView = iconView; - } - if (mTextView == null) { - TextView textView = (TextView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_text, this, false); - addView(textView); - mTextView = textView; - mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView); - } - TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance); - if (mTabTextColors != null) { - mTextView.setTextColor(mTabTextColors); - } - updateTextAndIcon(mTextView, mIconView); - } else { - // Else, we'll see if there is a TextView or ImageView present and update them - if (mCustomTextView != null || mCustomIconView != null) { - updateTextAndIcon(mCustomTextView, mCustomIconView); - } - } - - // Finally update our selected state - setSelected(tab != null && tab.isSelected()); - } - - private void updateTextAndIcon(@Nullable final TextView textView, - @Nullable final ImageView iconView) { - final Drawable icon = mTab != null ? mTab.getIcon() : null; - final CharSequence text = mTab != null ? mTab.getText() : null; - final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null; - - if (iconView != null) { - if (icon != null) { - iconView.setImageDrawable(icon); - iconView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - iconView.setVisibility(GONE); - iconView.setImageDrawable(null); - } - iconView.setContentDescription(contentDesc); - } - - final boolean hasText = !TextUtils.isEmpty(text); - if (textView != null) { - if (hasText) { - textView.setText(text); - textView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - textView.setVisibility(GONE); - textView.setText(null); - } - textView.setContentDescription(contentDesc); - } - - if (iconView != null) { - MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams()); - int bottomMargin = 0; - if (hasText && iconView.getVisibility() == VISIBLE) { - // If we're showing both text and icon, add some margin bottom to the icon - bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON); - } - if (bottomMargin != lp.bottomMargin) { - lp.bottomMargin = bottomMargin; - iconView.requestLayout(); - } - } - TooltipCompat.setTooltipText(this, hasText ? null : contentDesc); - } - - public Tab getTab() { - return mTab; - } - - /** - * Approximates a given lines width with the new provided text size. - */ - private float approximateLineWidth(Layout layout, int line, float textSize) { - return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize()); - } - } - - private class SlidingTabStrip extends LinearLayout { - private int mSelectedIndicatorHeight; - private final Paint mSelectedIndicatorPaint; - - int mSelectedPosition = -1; - float mSelectionOffset; - - private int mLayoutDirection = -1; - - private int mIndicatorLeft = -1; - private int mIndicatorRight = -1; - - private ValueAnimator mIndicatorAnimator; - - SlidingTabStrip(Context context) { - super(context); - setWillNotDraw(false); - mSelectedIndicatorPaint = new Paint(); - } - - void setSelectedIndicatorColor(int color) { - if (mSelectedIndicatorPaint.getColor() != color) { - mSelectedIndicatorPaint.setColor(color); - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void setSelectedIndicatorHeight(int height) { - if (mSelectedIndicatorHeight != height) { - mSelectedIndicatorHeight = height; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - boolean childrenNeedLayout() { - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - if (child.getWidth() <= 0) { - return true; - } - } - return false; - } - - void setIndicatorPositionFromTabPosition(int position, float positionOffset) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - mSelectedPosition = position; - mSelectionOffset = positionOffset; - updateIndicatorPosition(); - } - - float getIndicatorPosition() { - return mSelectedPosition + mSelectionOffset; - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - - // Workaround for a bug before Android M where LinearLayout did not relayout itself when - // layout direction changed. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - //noinspection WrongConstant - if (mLayoutDirection != layoutDirection) { - requestLayout(); - mLayoutDirection = layoutDirection; - } - } - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { - // HorizontalScrollView will first measure use with UNSPECIFIED, and then with - // EXACTLY. Ignore the first call since anything we do will be overwritten anyway - return; - } - - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) { - final int count = getChildCount(); - - // First we'll find the widest tab - int largestTabWidth = 0; - for (int i = 0, z = count; i < z; i++) { - View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth()); - } - } - - if (largestTabWidth <= 0) { - // If we don't have a largest child yet, skip until the next measure pass - return; - } - - final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN); - boolean remeasure = false; - - if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) { - // If the tabs fit within our width minus gutters, we will set all tabs to have - // the same width - for (int i = 0; i < count; i++) { - final LinearLayout.LayoutParams lp = - (LayoutParams) getChildAt(i).getLayoutParams(); - if (lp.width != largestTabWidth || lp.weight != 0) { - lp.width = largestTabWidth; - lp.weight = 0; - remeasure = true; - } - } - } else { - // If the tabs will wrap to be larger than the width minus gutters, we need - // to switch to GRAVITY_FILL - mTabGravity = GRAVITY_FILL; - updateTabViews(false); - remeasure = true; - } - - if (remeasure) { - // Now re-measure after our changes - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - // If we're currently running an animation, lets cancel it and start a - // new animation with the remaining duration - mIndicatorAnimator.cancel(); - final long duration = mIndicatorAnimator.getDuration(); - animateIndicatorToPosition(mSelectedPosition, - Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration)); - } else { - // If we've been layed out, update the indicator position - updateIndicatorPosition(); - } - } - - private void updateIndicatorPosition() { - final View selectedTitle = getChildAt(mSelectedPosition); - int left, right; - - if (selectedTitle != null && selectedTitle.getWidth() > 0) { - left = selectedTitle.getLeft(); - right = selectedTitle.getRight(); - - if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) { - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mSelectedPosition + 1); - left = (int) (mSelectionOffset * nextTitle.getLeft() + - (1.0f - mSelectionOffset) * left); - right = (int) (mSelectionOffset * nextTitle.getRight() + - (1.0f - mSelectionOffset) * right); - } - } else { - left = right = -1; - } - - setIndicatorPosition(left, right); - } - - void setIndicatorPosition(int left, int right) { - if (left != mIndicatorLeft || right != mIndicatorRight) { - // If the indicator's left/right has changed, invalidate - mIndicatorLeft = left; - mIndicatorRight = right; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void animateIndicatorToPosition(final int position, int duration) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - final boolean isRtl = ViewCompat.getLayoutDirection(this) - == ViewCompat.LAYOUT_DIRECTION_RTL; - - final View targetView = getChildAt(position); - if (targetView == null) { - // If we don't have a view, just update the position now and return - updateIndicatorPosition(); - return; - } - - final int targetLeft = targetView.getLeft(); - final int targetRight = targetView.getRight(); - final int startLeft; - final int startRight; - - if (Math.abs(position - mSelectedPosition) <= 1) { - // If the views are adjacent, we'll animate from edge-to-edge - startLeft = mIndicatorLeft; - startRight = mIndicatorRight; - } else { - // Else, we'll just grow from the nearest edge - final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET); - if (position < mSelectedPosition) { - // We're going end-to-start - if (isRtl) { - startLeft = startRight = targetLeft - offset; - } else { - startLeft = startRight = targetRight + offset; - } - } else { - // We're going start-to-end - if (isRtl) { - startLeft = startRight = targetRight + offset; - } else { - startLeft = startRight = targetLeft - offset; - } - } - } - - if (startLeft != targetLeft || startRight != targetRight) { - ValueAnimator animator = mIndicatorAnimator = new ValueAnimator(); - animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - animator.setDuration(duration); - animator.setFloatValues(0, 1); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - final float fraction = animator.getAnimatedFraction(); - setIndicatorPosition( - AnimationUtils.lerp(startLeft, targetLeft, fraction), - AnimationUtils.lerp(startRight, targetRight, fraction)); - } - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mSelectedPosition = position; - mSelectionOffset = 0f; - } - }); - animator.start(); - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - // Thick colored underline below the current selection - if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) { - canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight, - mIndicatorRight, getHeight(), mSelectedIndicatorPaint); - } - } - } - - private static ColorStateList createColorStateList(int defaultColor, int selectedColor) { - final int[][] states = new int[2][]; - final int[] colors = new int[2]; - int i = 0; - - states[i] = SELECTED_STATE_SET; - colors[i] = selectedColor; - i++; - - // Default enabled state - states[i] = EMPTY_STATE_SET; - colors[i] = defaultColor; - i++; - - return new ColorStateList(states, colors); - } - - private int getDefaultHeight() { - boolean hasIconAndText = false; - for (int i = 0, count = mTabs.size(); i < count; i++) { - Tab tab = mTabs.get(i); - if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) { - hasIconAndText = true; - break; - } - } - return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT; - } - - private int getTabMinWidth() { - if (mRequestedTabMinWidth != INVALID_WIDTH) { - // If we have been given a min width, use it - return mRequestedTabMinWidth; - } - // Else, we'll use the default value - return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0; - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - // We don't care about the layout params of any views added to us, since we don't actually - // add them. The only view we add is the SlidingTabStrip, which is done manually. - // We return the default layout params so that we don't blow up if we're given a TabItem - // without android:layout_* values. - return generateDefaultLayoutParams(); - } - - int getTabMaxWidth() { - return mTabMaxWidth; - } - - /** - * A {@link ViewPager.OnPageChangeListener} class which contains the - * necessary calls back to the provided {@link TabLayout} so that the tab position is - * kept in sync. - * - * <p>This class stores the provided TabLayout weakly, meaning that you can use - * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener) - * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and - * not cause a leak. - */ - public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener { - private final WeakReference<TabLayout> mTabLayoutRef; - private int mPreviousScrollState; - private int mScrollState; - - public TabLayoutOnPageChangeListener(TabLayout tabLayout) { - mTabLayoutRef = new WeakReference<>(tabLayout); - } - - @Override - public void onPageScrollStateChanged(final int state) { - mPreviousScrollState = mScrollState; - mScrollState = state; - } - - @Override - public void onPageScrolled(final int position, final float positionOffset, - final int positionOffsetPixels) { - final TabLayout tabLayout = mTabLayoutRef.get(); - if (tabLayout != null) { - // Only update the text selection if we're not settling, or we are settling after - // being dragged - final boolean updateText = mScrollState != SCROLL_STATE_SETTLING || - mPreviousScrollState == SCROLL_STATE_DRAGGING; - // Update the indicator if we're not settling after being idle. This is caused - // from a setCurrentItem() call and will be handled by an animation from - // onPageSelected() instead. - final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING - && mPreviousScrollState == SCROLL_STATE_IDLE); - tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator); - } - } - - @Override - public void onPageSelected(final int position) { - final TabLayout tabLayout = mTabLayoutRef.get(); - if (tabLayout != null && tabLayout.getSelectedTabPosition() != position - && position < tabLayout.getTabCount()) { - // Select the tab, only updating the indicator if we're not being dragged/settled - // (since onPageScrolled will handle that). - final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE - || (mScrollState == SCROLL_STATE_SETTLING - && mPreviousScrollState == SCROLL_STATE_IDLE); - tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator); - } - } - - void reset() { - mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE; - } - } - - /** - * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back - * to the provided {@link ViewPager} so that the tab position is kept in sync. - */ - public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener { - private final ViewPager mViewPager; - - public ViewPagerOnTabSelectedListener(ViewPager viewPager) { - mViewPager = viewPager; - } - - @Override - public void onTabSelected(TabLayout.Tab tab) { - mViewPager.setCurrentItem(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - // No-op - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - // No-op - } - } - - private class PagerAdapterObserver extends DataSetObserver { - PagerAdapterObserver() { - } - - @Override - public void onChanged() { - populateFromPagerAdapter(); - } - - @Override - public void onInvalidated() { - populateFromPagerAdapter(); - } - } - - private class AdapterChangeListener implements ViewPager.OnAdapterChangeListener { - private boolean mAutoRefresh; - - AdapterChangeListener() { - } - - @Override - public void onAdapterChanged(@NonNull ViewPager viewPager, - @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) { - if (mViewPager == viewPager) { - setPagerAdapter(newAdapter, mAutoRefresh); - } - } - - void setAutoRefresh(boolean autoRefresh) { - mAutoRefresh = autoRefresh; - } - } -} diff --git a/android/support/design/widget/TextInputEditText.java b/android/support/design/widget/TextInputEditText.java deleted file mode 100644 index ee6c32cd..00000000 --- a/android/support/design/widget/TextInputEditText.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 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.content.Context; -import android.support.v7.widget.AppCompatEditText; -import android.support.v7.widget.WithHint; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewParent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; - -/** - * A special sub-class of {@link android.widget.EditText} designed for use as a child of - * {@link TextInputLayout}. - * - * <p>Using this class allows us to display a hint in the IME when in 'extract' mode.</p> - */ -public class TextInputEditText extends AppCompatEditText { - - public TextInputEditText(Context context) { - super(context); - } - - public TextInputEditText(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public TextInputEditText(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - final InputConnection ic = super.onCreateInputConnection(outAttrs); - if (ic != null && outAttrs.hintText == null) { - // If we don't have a hint and our parent implements WithHint, use its hint for the - // EditorInfo. This allows us to display a hint in 'extract mode'. - ViewParent parent = getParent(); - while (parent instanceof View) { - if (parent instanceof WithHint) { - outAttrs.hintText = ((WithHint) parent).getHint(); - break; - } - parent = parent.getParent(); - } - } - return ic; - } -} diff --git a/android/support/design/widget/TextInputLayout.java b/android/support/design/widget/TextInputLayout.java deleted file mode 100644 index 0540678e..00000000 --- a/android/support/design/widget/TextInputLayout.java +++ /dev/null @@ -1,1530 +0,0 @@ -/* - * 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.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.DrawableContainer; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.DrawableRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.annotation.StyleRes; -import android.support.annotation.VisibleForTesting; -import android.support.design.R; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.view.AbsSavedState; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.GravityCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v4.widget.Space; -import android.support.v4.widget.TextViewCompat; -import android.support.v4.widget.ViewGroupUtils; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.widget.AppCompatDrawableManager; -import android.support.v7.widget.AppCompatTextView; -import android.support.v7.widget.TintTypedArray; -import android.support.v7.widget.WithHint; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.PasswordTransformationMethod; -import android.util.AttributeSet; -import android.util.Log; -import android.util.SparseArray; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewStructure; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AccelerateInterpolator; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; - -/** - * Layout which wraps an {@link android.widget.EditText} (or descendant) to show a floating label - * when the hint is hidden due to the user inputting text. - * - * <p>Also supports showing an error via {@link #setErrorEnabled(boolean)} and - * {@link #setError(CharSequence)}, and a character counter via - * {@link #setCounterEnabled(boolean)}.</p> - * - * <p>Password visibility toggling is also supported via the - * {@link #setPasswordVisibilityToggleEnabled(boolean)} API and related attribute. - * If enabled, a button is displayed to toggle between the password being displayed as plain-text - * or disguised, when your EditText is set to display a password.</p> - * - * <p><strong>Note:</strong> When using the password toggle functionality, the 'end' compound - * drawable of the EditText will be overridden while the toggle is enabled. To ensure that any - * existing drawables are restored correctly, you should set those compound drawables relatively - * (start/end), opposed to absolutely (left/right).</p> - * - * The {@link TextInputEditText} class is provided to be used as a child of this layout. Using - * TextInputEditText allows TextInputLayout greater control over the visual aspects of any - * text input. An example usage is as so: - * - * <pre> - * <android.support.design.widget.TextInputLayout - * android:layout_width="match_parent" - * android:layout_height="wrap_content"> - * - * <android.support.design.widget.TextInputEditText - * android:layout_width="match_parent" - * android:layout_height="wrap_content" - * android:hint="@string/form_username"/> - * - * </android.support.design.widget.TextInputLayout> - * </pre> - * - * <p><strong>Note:</strong> The actual view hierarchy present under TextInputLayout is - * <strong>NOT</strong> guaranteed to match the view hierarchy as written in XML. As a result, - * calls to getParent() on children of the TextInputLayout -- such as an TextInputEditText -- - * may not return the TextInputLayout itself, but rather an intermediate View. If you need - * to access a View directly, set an {@code android:id} and use {@link View#findViewById(int)}. - */ -public class TextInputLayout extends LinearLayout implements WithHint { - - private static final int ANIMATION_DURATION = 200; - private static final int INVALID_MAX_LENGTH = -1; - - private static final String LOG_TAG = "TextInputLayout"; - - private final FrameLayout mInputFrame; - EditText mEditText; - private CharSequence mOriginalHint; - - private boolean mHintEnabled; - private CharSequence mHint; - - private Paint mTmpPaint; - private final Rect mTmpRect = new Rect(); - - private LinearLayout mIndicatorArea; - private int mIndicatorsAdded; - - private Typeface mTypeface; - - private boolean mErrorEnabled; - TextView mErrorView; - private int mErrorTextAppearance; - private boolean mErrorShown; - private CharSequence mError; - - boolean mCounterEnabled; - private TextView mCounterView; - private int mCounterMaxLength; - private int mCounterTextAppearance; - private int mCounterOverflowTextAppearance; - private boolean mCounterOverflowed; - - private boolean mPasswordToggleEnabled; - private Drawable mPasswordToggleDrawable; - private CharSequence mPasswordToggleContentDesc; - private CheckableImageButton mPasswordToggleView; - private boolean mPasswordToggledVisible; - private Drawable mPasswordToggleDummyDrawable; - private Drawable mOriginalEditTextEndDrawable; - - private ColorStateList mPasswordToggleTintList; - private boolean mHasPasswordToggleTintList; - private PorterDuff.Mode mPasswordToggleTintMode; - private boolean mHasPasswordToggleTintMode; - - private ColorStateList mDefaultTextColor; - private ColorStateList mFocusedTextColor; - - // Only used for testing - private boolean mHintExpanded; - - final CollapsingTextHelper mCollapsingTextHelper = new CollapsingTextHelper(this); - - private boolean mHintAnimationEnabled; - private ValueAnimator mAnimator; - - private boolean mHasReconstructedEditTextBackground; - private boolean mInDrawableStateChanged; - - private boolean mRestoringSavedState; - - public TextInputLayout(Context context) { - this(context, null); - } - - public TextInputLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) { - // Can't call through to super(Context, AttributeSet, int) since it doesn't exist on API 10 - super(context, attrs); - - ThemeUtils.checkAppCompatTheme(context); - - setOrientation(VERTICAL); - setWillNotDraw(false); - setAddStatesFromChildren(true); - - mInputFrame = new FrameLayout(context); - mInputFrame.setAddStatesFromChildren(true); - addView(mInputFrame); - - mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - mCollapsingTextHelper.setPositionInterpolator(new AccelerateInterpolator()); - mCollapsingTextHelper.setCollapsedTextGravity(Gravity.TOP | GravityCompat.START); - - final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, - R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout); - mHintEnabled = a.getBoolean(R.styleable.TextInputLayout_hintEnabled, true); - setHint(a.getText(R.styleable.TextInputLayout_android_hint)); - mHintAnimationEnabled = a.getBoolean( - R.styleable.TextInputLayout_hintAnimationEnabled, true); - - if (a.hasValue(R.styleable.TextInputLayout_android_textColorHint)) { - mDefaultTextColor = mFocusedTextColor = - a.getColorStateList(R.styleable.TextInputLayout_android_textColorHint); - } - - final int hintAppearance = a.getResourceId( - R.styleable.TextInputLayout_hintTextAppearance, -1); - if (hintAppearance != -1) { - setHintTextAppearance( - a.getResourceId(R.styleable.TextInputLayout_hintTextAppearance, 0)); - } - - mErrorTextAppearance = a.getResourceId(R.styleable.TextInputLayout_errorTextAppearance, 0); - final boolean errorEnabled = a.getBoolean(R.styleable.TextInputLayout_errorEnabled, false); - - final boolean counterEnabled = a.getBoolean( - R.styleable.TextInputLayout_counterEnabled, false); - setCounterMaxLength( - a.getInt(R.styleable.TextInputLayout_counterMaxLength, INVALID_MAX_LENGTH)); - mCounterTextAppearance = a.getResourceId( - R.styleable.TextInputLayout_counterTextAppearance, 0); - mCounterOverflowTextAppearance = a.getResourceId( - R.styleable.TextInputLayout_counterOverflowTextAppearance, 0); - - mPasswordToggleEnabled = a.getBoolean( - R.styleable.TextInputLayout_passwordToggleEnabled, false); - mPasswordToggleDrawable = a.getDrawable(R.styleable.TextInputLayout_passwordToggleDrawable); - mPasswordToggleContentDesc = a.getText( - R.styleable.TextInputLayout_passwordToggleContentDescription); - if (a.hasValue(R.styleable.TextInputLayout_passwordToggleTint)) { - mHasPasswordToggleTintList = true; - mPasswordToggleTintList = a.getColorStateList( - R.styleable.TextInputLayout_passwordToggleTint); - } - if (a.hasValue(R.styleable.TextInputLayout_passwordToggleTintMode)) { - mHasPasswordToggleTintMode = true; - mPasswordToggleTintMode = ViewUtils.parseTintMode( - a.getInt(R.styleable.TextInputLayout_passwordToggleTintMode, -1), null); - } - - a.recycle(); - - setErrorEnabled(errorEnabled); - setCounterEnabled(counterEnabled); - applyPasswordToggleTint(); - - if (ViewCompat.getImportantForAccessibility(this) - == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - // Make sure we're important for accessibility if we haven't been explicitly not - ViewCompat.setImportantForAccessibility(this, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - ViewCompat.setAccessibilityDelegate(this, new TextInputAccessibilityDelegate()); - } - - @Override - public void addView(View child, int index, final ViewGroup.LayoutParams params) { - if (child instanceof EditText) { - // Make sure that the EditText is vertically at the bottom, so that it sits on the - // EditText's underline - FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params); - flp.gravity = Gravity.CENTER_VERTICAL | (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK); - mInputFrame.addView(child, flp); - - // Now use the EditText's LayoutParams as our own and update them to make enough space - // for the label - mInputFrame.setLayoutParams(params); - updateInputLayoutMargins(); - - setEditText((EditText) child); - } else { - // Carry on adding the View... - super.addView(child, index, params); - } - } - - /** - * Set the typeface to use for the hint and any label views (such as counter and error views). - * - * @param typeface typeface to use, or {@code null} to use the default. - */ - public void setTypeface(@Nullable Typeface typeface) { - if ((mTypeface != null && !mTypeface.equals(typeface)) - || (mTypeface == null && typeface != null)) { - mTypeface = typeface; - - mCollapsingTextHelper.setTypefaces(typeface); - if (mCounterView != null) { - mCounterView.setTypeface(typeface); - } - if (mErrorView != null) { - mErrorView.setTypeface(typeface); - } - } - } - - /** - * Returns the typeface used for the hint and any label views (such as counter and error views). - */ - @NonNull - public Typeface getTypeface() { - return mTypeface; - } - - @Override - public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) { - if (mOriginalHint == null || mEditText == null) { - super.dispatchProvideAutofillStructure(structure, flags); - return; - } - - // Temporarily sets child's hint to its original value so it is properly set in the - // child's ViewStructure. - final CharSequence hint = mEditText.getHint(); - mEditText.setHint(mOriginalHint); - try { - super.dispatchProvideAutofillStructure(structure, flags); - } finally { - mEditText.setHint(hint); - } - } - - private void setEditText(EditText editText) { - // If we already have an EditText, throw an exception - if (mEditText != null) { - throw new IllegalArgumentException("We already have an EditText, can only have one"); - } - - if (!(editText instanceof TextInputEditText)) { - Log.i(LOG_TAG, "EditText added is not a TextInputEditText. Please switch to using that" - + " class instead."); - } - - mEditText = editText; - - final boolean hasPasswordTransformation = hasPasswordTransformation(); - - // Use the EditText's typeface, and it's text size for our expanded text - if (!hasPasswordTransformation) { - // We don't want a monospace font just because we have a password field - mCollapsingTextHelper.setTypefaces(mEditText.getTypeface()); - } - mCollapsingTextHelper.setExpandedTextSize(mEditText.getTextSize()); - - final int editTextGravity = mEditText.getGravity(); - mCollapsingTextHelper.setCollapsedTextGravity( - Gravity.TOP | (editTextGravity & ~Gravity.VERTICAL_GRAVITY_MASK)); - mCollapsingTextHelper.setExpandedTextGravity(editTextGravity); - - // Add a TextWatcher so that we know when the text input has changed - mEditText.addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - updateLabelState(!mRestoringSavedState); - if (mCounterEnabled) { - updateCounter(s.length()); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) {} - }); - - // Use the EditText's hint colors if we don't have one set - if (mDefaultTextColor == null) { - mDefaultTextColor = mEditText.getHintTextColors(); - } - - // If we do not have a valid hint, try and retrieve it from the EditText, if enabled - if (mHintEnabled && TextUtils.isEmpty(mHint)) { - // Save the hint so it can be restored on dispatchProvideAutofillStructure(); - mOriginalHint = mEditText.getHint(); - setHint(mOriginalHint); - // Clear the EditText's hint as we will display it ourselves - mEditText.setHint(null); - } - - if (mCounterView != null) { - updateCounter(mEditText.getText().length()); - } - - if (mIndicatorArea != null) { - adjustIndicatorPadding(); - } - - updatePasswordToggleView(); - - // Update the label visibility with no animation, but force a state change - updateLabelState(false, true); - } - - private void updateInputLayoutMargins() { - // Create/update the LayoutParams so that we can add enough top margin - // to the EditText so make room for the label - final LayoutParams lp = (LayoutParams) mInputFrame.getLayoutParams(); - final int newTopMargin; - - if (mHintEnabled) { - if (mTmpPaint == null) { - mTmpPaint = new Paint(); - } - mTmpPaint.setTypeface(mCollapsingTextHelper.getCollapsedTypeface()); - mTmpPaint.setTextSize(mCollapsingTextHelper.getCollapsedTextSize()); - newTopMargin = (int) -mTmpPaint.ascent(); - } else { - newTopMargin = 0; - } - - if (newTopMargin != lp.topMargin) { - lp.topMargin = newTopMargin; - mInputFrame.requestLayout(); - } - } - - void updateLabelState(boolean animate) { - updateLabelState(animate, false); - } - - void updateLabelState(final boolean animate, final boolean force) { - final boolean isEnabled = isEnabled(); - final boolean hasText = mEditText != null && !TextUtils.isEmpty(mEditText.getText()); - final boolean isFocused = arrayContains(getDrawableState(), android.R.attr.state_focused); - final boolean isErrorShowing = !TextUtils.isEmpty(getError()); - - if (mDefaultTextColor != null) { - mCollapsingTextHelper.setExpandedTextColor(mDefaultTextColor); - } - - if (isEnabled && mCounterOverflowed && mCounterView != null) { - mCollapsingTextHelper.setCollapsedTextColor(mCounterView.getTextColors()); - } else if (isEnabled && isFocused && mFocusedTextColor != null) { - mCollapsingTextHelper.setCollapsedTextColor(mFocusedTextColor); - } else if (mDefaultTextColor != null) { - mCollapsingTextHelper.setCollapsedTextColor(mDefaultTextColor); - } - - if (hasText || (isEnabled() && (isFocused || isErrorShowing))) { - // We should be showing the label so do so if it isn't already - if (force || mHintExpanded) { - collapseHint(animate); - } - } else { - // We should not be showing the label so hide it - if (force || !mHintExpanded) { - expandHint(animate); - } - } - } - - /** - * Returns the {@link android.widget.EditText} used for text input. - */ - @Nullable - public EditText getEditText() { - return mEditText; - } - - /** - * Set the hint to be displayed in the floating label, if enabled. - * - * @see #setHintEnabled(boolean) - * - * @attr ref android.support.design.R.styleable#TextInputLayout_android_hint - */ - public void setHint(@Nullable CharSequence hint) { - if (mHintEnabled) { - setHintInternal(hint); - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - } - } - - private void setHintInternal(CharSequence hint) { - mHint = hint; - mCollapsingTextHelper.setText(hint); - } - - /** - * Returns the hint which is displayed in the floating label, if enabled. - * - * @return the hint, or null if there isn't one set, or the hint is not enabled. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_android_hint - */ - @Override - @Nullable - public CharSequence getHint() { - return mHintEnabled ? mHint : null; - } - - /** - * Sets whether the floating label functionality is enabled or not in this layout. - * - * <p>If enabled, any non-empty hint in the child EditText will be moved into the floating - * hint, and its existing hint will be cleared. If disabled, then any non-empty floating hint - * in this layout will be moved into the EditText, and this layout's hint will be cleared.</p> - * - * @see #setHint(CharSequence) - * @see #isHintEnabled() - * - * @attr ref android.support.design.R.styleable#TextInputLayout_hintEnabled - */ - public void setHintEnabled(boolean enabled) { - if (enabled != mHintEnabled) { - mHintEnabled = enabled; - - final CharSequence editTextHint = mEditText.getHint(); - if (!mHintEnabled) { - if (!TextUtils.isEmpty(mHint) && TextUtils.isEmpty(editTextHint)) { - // If the hint is disabled, but we have a hint set, and the EditText doesn't, - // pass it through... - mEditText.setHint(mHint); - } - // Now clear out any set hint - setHintInternal(null); - } else { - if (!TextUtils.isEmpty(editTextHint)) { - // If the hint is now enabled and the EditText has one set, we'll use it if - // we don't already have one, and clear the EditText's - if (TextUtils.isEmpty(mHint)) { - setHint(editTextHint); - } - mEditText.setHint(null); - } - } - - // Now update the EditText top margin - if (mEditText != null) { - updateInputLayoutMargins(); - } - } - } - - /** - * Returns whether the floating label functionality is enabled or not in this layout. - * - * @see #setHintEnabled(boolean) - * - * @attr ref android.support.design.R.styleable#TextInputLayout_hintEnabled - */ - public boolean isHintEnabled() { - return mHintEnabled; - } - - /** - * Sets the hint text color, size, style from the specified TextAppearance resource. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_hintTextAppearance - */ - public void setHintTextAppearance(@StyleRes int resId) { - mCollapsingTextHelper.setCollapsedTextAppearance(resId); - mFocusedTextColor = mCollapsingTextHelper.getCollapsedTextColor(); - - if (mEditText != null) { - updateLabelState(false); - // Text size might have changed so update the top margin - updateInputLayoutMargins(); - } - } - - private void addIndicator(TextView indicator, int index) { - if (mIndicatorArea == null) { - mIndicatorArea = new LinearLayout(getContext()); - mIndicatorArea.setOrientation(LinearLayout.HORIZONTAL); - addView(mIndicatorArea, LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - - // Add a flexible spacer in the middle so that the left/right views stay pinned - final Space spacer = new Space(getContext()); - final LinearLayout.LayoutParams spacerLp = new LinearLayout.LayoutParams(0, 0, 1f); - mIndicatorArea.addView(spacer, spacerLp); - - if (mEditText != null) { - adjustIndicatorPadding(); - } - } - mIndicatorArea.setVisibility(View.VISIBLE); - mIndicatorArea.addView(indicator, index); - mIndicatorsAdded++; - } - - private void adjustIndicatorPadding() { - // Add padding to the error and character counter so that they match the EditText - ViewCompat.setPaddingRelative(mIndicatorArea, ViewCompat.getPaddingStart(mEditText), - 0, ViewCompat.getPaddingEnd(mEditText), mEditText.getPaddingBottom()); - } - - private void removeIndicator(TextView indicator) { - if (mIndicatorArea != null) { - mIndicatorArea.removeView(indicator); - if (--mIndicatorsAdded == 0) { - mIndicatorArea.setVisibility(View.GONE); - } - } - } - - /** - * Whether the error functionality is enabled or not in this layout. Enabling this - * functionality before setting an error message via {@link #setError(CharSequence)}, will mean - * that this layout will not change size when an error is displayed. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_errorEnabled - */ - public void setErrorEnabled(boolean enabled) { - if (mErrorEnabled != enabled) { - if (mErrorView != null) { - mErrorView.animate().cancel(); - } - - if (enabled) { - mErrorView = new AppCompatTextView(getContext()); - mErrorView.setId(R.id.textinput_error); - if (mTypeface != null) { - mErrorView.setTypeface(mTypeface); - } - boolean useDefaultColor = false; - try { - TextViewCompat.setTextAppearance(mErrorView, mErrorTextAppearance); - - if (Build.VERSION.SDK_INT >= 23 - && mErrorView.getTextColors().getDefaultColor() == Color.MAGENTA) { - // Caused by our theme not extending from Theme.Design*. On API 23 and - // above, unresolved theme attrs result in MAGENTA rather than an exception. - // Flag so that we use a decent default - useDefaultColor = true; - } - } catch (Exception e) { - // Caused by our theme not extending from Theme.Design*. Flag so that we use - // a decent default - useDefaultColor = true; - } - if (useDefaultColor) { - // Probably caused by our theme not extending from Theme.Design*. Instead - // we manually set something appropriate - TextViewCompat.setTextAppearance(mErrorView, - android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption); - mErrorView.setTextColor(ContextCompat.getColor(getContext(), - android.support.v7.appcompat.R.color.error_color_material)); - } - mErrorView.setVisibility(INVISIBLE); - ViewCompat.setAccessibilityLiveRegion(mErrorView, - ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE); - addIndicator(mErrorView, 0); - } else { - mErrorShown = false; - updateEditTextBackground(); - removeIndicator(mErrorView); - mErrorView = null; - } - mErrorEnabled = enabled; - } - } - - /** - * Sets the text color and size for the error message from the specified - * TextAppearance resource. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_errorTextAppearance - */ - public void setErrorTextAppearance(@StyleRes int resId) { - mErrorTextAppearance = resId; - if (mErrorView != null) { - TextViewCompat.setTextAppearance(mErrorView, resId); - } - } - - /** - * Returns whether the error functionality is enabled or not in this layout. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_errorEnabled - * - * @see #setErrorEnabled(boolean) - */ - public boolean isErrorEnabled() { - return mErrorEnabled; - } - - /** - * Sets an error message that will be displayed below our {@link EditText}. If the - * {@code error} is {@code null}, the error message will be cleared. - * <p> - * If the error functionality has not been enabled via {@link #setErrorEnabled(boolean)}, then - * it will be automatically enabled if {@code error} is not empty. - * - * @param error Error message to display, or null to clear - * - * @see #getError() - */ - public void setError(@Nullable final CharSequence error) { - // Only animate if we're enabled, laid out, and we have a different error message - setError(error, ViewCompat.isLaidOut(this) && isEnabled() - && (mErrorView == null || !TextUtils.equals(mErrorView.getText(), error))); - } - - private void setError(@Nullable final CharSequence error, final boolean animate) { - mError = error; - - if (!mErrorEnabled) { - if (TextUtils.isEmpty(error)) { - // If error isn't enabled, and the error is empty, just return - return; - } - // Else, we'll assume that they want to enable the error functionality - setErrorEnabled(true); - } - - mErrorShown = !TextUtils.isEmpty(error); - - // Cancel any on-going animation - mErrorView.animate().cancel(); - - if (mErrorShown) { - mErrorView.setText(error); - mErrorView.setVisibility(VISIBLE); - - if (animate) { - if (mErrorView.getAlpha() == 1f) { - // If it's currently 100% show, we'll animate it from 0 - mErrorView.setAlpha(0f); - } - mErrorView.animate() - .alpha(1f) - .setDuration(ANIMATION_DURATION) - .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - mErrorView.setVisibility(VISIBLE); - } - }).start(); - } else { - // Set alpha to 1f, just in case - mErrorView.setAlpha(1f); - } - } else { - if (mErrorView.getVisibility() == VISIBLE) { - if (animate) { - mErrorView.animate() - .alpha(0f) - .setDuration(ANIMATION_DURATION) - .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mErrorView.setText(error); - mErrorView.setVisibility(INVISIBLE); - } - }).start(); - } else { - mErrorView.setText(error); - mErrorView.setVisibility(INVISIBLE); - } - } - } - - updateEditTextBackground(); - updateLabelState(animate); - } - - /** - * Whether the character counter functionality is enabled or not in this layout. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_counterEnabled - */ - public void setCounterEnabled(boolean enabled) { - if (mCounterEnabled != enabled) { - if (enabled) { - mCounterView = new AppCompatTextView(getContext()); - mCounterView.setId(R.id.textinput_counter); - if (mTypeface != null) { - mCounterView.setTypeface(mTypeface); - } - mCounterView.setMaxLines(1); - try { - TextViewCompat.setTextAppearance(mCounterView, mCounterTextAppearance); - } catch (Exception e) { - // Probably caused by our theme not extending from Theme.Design*. Instead - // we manually set something appropriate - TextViewCompat.setTextAppearance(mCounterView, - android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption); - mCounterView.setTextColor(ContextCompat.getColor(getContext(), - android.support.v7.appcompat.R.color.error_color_material)); - } - addIndicator(mCounterView, -1); - if (mEditText == null) { - updateCounter(0); - } else { - updateCounter(mEditText.getText().length()); - } - } else { - removeIndicator(mCounterView); - mCounterView = null; - } - mCounterEnabled = enabled; - } - } - - /** - * Returns whether the character counter functionality is enabled or not in this layout. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_counterEnabled - * - * @see #setCounterEnabled(boolean) - */ - public boolean isCounterEnabled() { - return mCounterEnabled; - } - - /** - * Sets the max length to display at the character counter. - * - * @param maxLength maxLength to display. Any value less than or equal to 0 will not be shown. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_counterMaxLength - */ - public void setCounterMaxLength(int maxLength) { - if (mCounterMaxLength != maxLength) { - if (maxLength > 0) { - mCounterMaxLength = maxLength; - } else { - mCounterMaxLength = INVALID_MAX_LENGTH; - } - if (mCounterEnabled) { - updateCounter(mEditText == null ? 0 : mEditText.getText().length()); - } - } - } - - @Override - public void setEnabled(boolean enabled) { - // Since we're set to addStatesFromChildren, we need to make sure that we set all - // children to enabled/disabled otherwise any enabled children will wipe out our disabled - // drawable state - recursiveSetEnabled(this, enabled); - super.setEnabled(enabled); - } - - private static void recursiveSetEnabled(final ViewGroup vg, final boolean enabled) { - for (int i = 0, count = vg.getChildCount(); i < count; i++) { - final View child = vg.getChildAt(i); - child.setEnabled(enabled); - if (child instanceof ViewGroup) { - recursiveSetEnabled((ViewGroup) child, enabled); - } - } - } - - /** - * Returns the max length shown at the character counter. - * - * @attr ref android.support.design.R.styleable#TextInputLayout_counterMaxLength - */ - public int getCounterMaxLength() { - return mCounterMaxLength; - } - - void updateCounter(int length) { - boolean wasCounterOverflowed = mCounterOverflowed; - if (mCounterMaxLength == INVALID_MAX_LENGTH) { - mCounterView.setText(String.valueOf(length)); - mCounterOverflowed = false; - } else { - mCounterOverflowed = length > mCounterMaxLength; - if (wasCounterOverflowed != mCounterOverflowed) { - TextViewCompat.setTextAppearance(mCounterView, mCounterOverflowed - ? mCounterOverflowTextAppearance : mCounterTextAppearance); - } - mCounterView.setText(getContext().getString(R.string.character_counter_pattern, - length, mCounterMaxLength)); - } - if (mEditText != null && wasCounterOverflowed != mCounterOverflowed) { - updateLabelState(false); - updateEditTextBackground(); - } - } - - private void updateEditTextBackground() { - if (mEditText == null) { - return; - } - - Drawable editTextBackground = mEditText.getBackground(); - if (editTextBackground == null) { - return; - } - - ensureBackgroundDrawableStateWorkaround(); - - if (android.support.v7.widget.DrawableUtils.canSafelyMutateDrawable(editTextBackground)) { - editTextBackground = editTextBackground.mutate(); - } - - if (mErrorShown && mErrorView != null) { - // Set a color filter of the error color - editTextBackground.setColorFilter( - AppCompatDrawableManager.getPorterDuffColorFilter( - mErrorView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN)); - } else if (mCounterOverflowed && mCounterView != null) { - // Set a color filter of the counter color - editTextBackground.setColorFilter( - AppCompatDrawableManager.getPorterDuffColorFilter( - mCounterView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN)); - } else { - // Else reset the color filter and refresh the drawable state so that the - // normal tint is used - DrawableCompat.clearColorFilter(editTextBackground); - mEditText.refreshDrawableState(); - } - } - - private void ensureBackgroundDrawableStateWorkaround() { - final int sdk = Build.VERSION.SDK_INT; - if (sdk != 21 && sdk != 22) { - // The workaround is only required on API 21-22 - return; - } - final Drawable bg = mEditText.getBackground(); - if (bg == null) { - return; - } - - if (!mHasReconstructedEditTextBackground) { - // This is gross. There is an issue in the platform which affects container Drawables - // where the first drawable retrieved from resources will propagate any changes - // (like color filter) to all instances from the cache. We'll try to workaround it... - - final Drawable newBg = bg.getConstantState().newDrawable(); - - if (bg instanceof DrawableContainer) { - // If we have a Drawable container, we can try and set it's constant state via - // reflection from the new Drawable - mHasReconstructedEditTextBackground = - DrawableUtils.setContainerConstantState( - (DrawableContainer) bg, newBg.getConstantState()); - } - - if (!mHasReconstructedEditTextBackground) { - // If we reach here then we just need to set a brand new instance of the Drawable - // as the background. This has the unfortunate side-effect of wiping out any - // user set padding, but I'd hope that use of custom padding on an EditText - // is limited. - ViewCompat.setBackground(mEditText, newBg); - mHasReconstructedEditTextBackground = true; - } - } - } - - static class SavedState extends AbsSavedState { - CharSequence error; - boolean isPasswordToggledVisible; - - SavedState(Parcelable superState) { - super(superState); - } - - SavedState(Parcel source, ClassLoader loader) { - super(source, loader); - error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - isPasswordToggledVisible = (source.readInt() == 1); - - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - TextUtils.writeToParcel(error, dest, flags); - dest.writeInt(isPasswordToggledVisible ? 1 : 0); - } - - @Override - public String toString() { - return "TextInputLayout.SavedState{" - + Integer.toHexString(System.identityHashCode(this)) - + " error=" + error + "}"; - } - - public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel in, ClassLoader loader) { - return new SavedState(in, loader); - } - - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in, null); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState ss = new SavedState(superState); - if (mErrorShown) { - ss.error = getError(); - } - ss.isPasswordToggledVisible = mPasswordToggledVisible; - return ss; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - setError(ss.error); - if (ss.isPasswordToggledVisible) { - passwordVisibilityToggleRequested(true); - } - requestLayout(); - } - - @Override - protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { - mRestoringSavedState = true; - super.dispatchRestoreInstanceState(container); - mRestoringSavedState = false; - } - - /** - * Returns the error message that was set to be displayed with - * {@link #setError(CharSequence)}, or <code>null</code> if no error was set - * or if error displaying is not enabled. - * - * @see #setError(CharSequence) - */ - @Nullable - public CharSequence getError() { - return mErrorEnabled ? mError : null; - } - - /** - * Returns whether any hint state changes, due to being focused or non-empty text, are - * animated. - * - * @see #setHintAnimationEnabled(boolean) - * - * @attr ref android.support.design.R.styleable#TextInputLayout_hintAnimationEnabled - */ - public boolean isHintAnimationEnabled() { - return mHintAnimationEnabled; - } - - /** - * Set whether any hint state changes, due to being focused or non-empty text, are - * animated. - * - * @see #isHintAnimationEnabled() - * - * @attr ref android.support.design.R.styleable#TextInputLayout_hintAnimationEnabled - */ - public void setHintAnimationEnabled(boolean enabled) { - mHintAnimationEnabled = enabled; - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - if (mHintEnabled) { - mCollapsingTextHelper.draw(canvas); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - updatePasswordToggleView(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private void updatePasswordToggleView() { - if (mEditText == null) { - // If there is no EditText, there is nothing to update - return; - } - - if (shouldShowPasswordIcon()) { - if (mPasswordToggleView == null) { - mPasswordToggleView = (CheckableImageButton) LayoutInflater.from(getContext()) - .inflate(R.layout.design_text_input_password_icon, mInputFrame, false); - mPasswordToggleView.setImageDrawable(mPasswordToggleDrawable); - mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc); - mInputFrame.addView(mPasswordToggleView); - - mPasswordToggleView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - passwordVisibilityToggleRequested(false); - } - }); - } - - if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) { - // We should make sure that the EditText has the same min-height as the password - // toggle view. This ensure focus works properly, and there is no visual jump - // if the password toggle is enabled/disabled. - mEditText.setMinimumHeight(ViewCompat.getMinimumHeight(mPasswordToggleView)); - } - - mPasswordToggleView.setVisibility(VISIBLE); - mPasswordToggleView.setChecked(mPasswordToggledVisible); - - // We need to add a dummy drawable as the end compound drawable so that the text is - // indented and doesn't display below the toggle view - if (mPasswordToggleDummyDrawable == null) { - mPasswordToggleDummyDrawable = new ColorDrawable(); - } - mPasswordToggleDummyDrawable.setBounds(0, 0, mPasswordToggleView.getMeasuredWidth(), 1); - - final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText); - // Store the user defined end compound drawable so that we can restore it later - if (compounds[2] != mPasswordToggleDummyDrawable) { - mOriginalEditTextEndDrawable = compounds[2]; - } - TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0], compounds[1], - mPasswordToggleDummyDrawable, compounds[3]); - - // Copy over the EditText's padding so that we match - mPasswordToggleView.setPadding(mEditText.getPaddingLeft(), - mEditText.getPaddingTop(), mEditText.getPaddingRight(), - mEditText.getPaddingBottom()); - } else { - if (mPasswordToggleView != null && mPasswordToggleView.getVisibility() == VISIBLE) { - mPasswordToggleView.setVisibility(View.GONE); - } - - if (mPasswordToggleDummyDrawable != null) { - // Make sure that we remove the dummy end compound drawable if it exists, and then - // clear it - final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText); - if (compounds[2] == mPasswordToggleDummyDrawable) { - TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0], - compounds[1], mOriginalEditTextEndDrawable, compounds[3]); - mPasswordToggleDummyDrawable = null; - } - } - } - } - - /** - * Set the icon to use for the password visibility toggle button. - * - * <p>If you use an icon you should also set a description for its action - * using {@link #setPasswordVisibilityToggleContentDescription(CharSequence)}. - * This is used for accessibility.</p> - * - * @param resId resource id of the drawable to set, or 0 to clear the icon - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleDrawable - */ - public void setPasswordVisibilityToggleDrawable(@DrawableRes int resId) { - setPasswordVisibilityToggleDrawable(resId != 0 - ? AppCompatResources.getDrawable(getContext(), resId) - : null); - } - - /** - * Set the icon to use for the password visibility toggle button. - * - * <p>If you use an icon you should also set a description for its action - * using {@link #setPasswordVisibilityToggleContentDescription(CharSequence)}. - * This is used for accessibility.</p> - * - * @param icon Drawable to set, may be null to clear the icon - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleDrawable - */ - public void setPasswordVisibilityToggleDrawable(@Nullable Drawable icon) { - mPasswordToggleDrawable = icon; - if (mPasswordToggleView != null) { - mPasswordToggleView.setImageDrawable(icon); - } - } - - /** - * Set a content description for the navigation button if one is present. - * - * <p>The content description will be read via screen readers or other accessibility - * systems to explain the action of the password visibility toggle.</p> - * - * @param resId Resource ID of a content description string to set, - * or 0 to clear the description - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleContentDescription - */ - public void setPasswordVisibilityToggleContentDescription(@StringRes int resId) { - setPasswordVisibilityToggleContentDescription( - resId != 0 ? getResources().getText(resId) : null); - } - - /** - * Set a content description for the navigation button if one is present. - * - * <p>The content description will be read via screen readers or other accessibility - * systems to explain the action of the password visibility toggle.</p> - * - * @param description Content description to set, or null to clear the content description - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleContentDescription - */ - public void setPasswordVisibilityToggleContentDescription(@Nullable CharSequence description) { - mPasswordToggleContentDesc = description; - if (mPasswordToggleView != null) { - mPasswordToggleView.setContentDescription(description); - } - } - - /** - * Returns the icon currently used for the password visibility toggle button. - * - * @see #setPasswordVisibilityToggleDrawable(Drawable) - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleDrawable - */ - @Nullable - public Drawable getPasswordVisibilityToggleDrawable() { - return mPasswordToggleDrawable; - } - - /** - * Returns the currently configured content description for the password visibility - * toggle button. - * - * <p>This will be used to describe the navigation action to users through mechanisms - * such as screen readers.</p> - */ - @Nullable - public CharSequence getPasswordVisibilityToggleContentDescription() { - return mPasswordToggleContentDesc; - } - - /** - * Returns whether the password visibility toggle functionality is currently enabled. - * - * @see #setPasswordVisibilityToggleEnabled(boolean) - */ - public boolean isPasswordVisibilityToggleEnabled() { - return mPasswordToggleEnabled; - } - - /** - * Returns whether the password visibility toggle functionality is enabled or not. - * - * <p>When enabled, a button is placed at the end of the EditText which enables the user - * to switch between the field's input being visibly disguised or not.</p> - * - * @param enabled true to enable the functionality - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleEnabled - */ - public void setPasswordVisibilityToggleEnabled(final boolean enabled) { - if (mPasswordToggleEnabled != enabled) { - mPasswordToggleEnabled = enabled; - - if (!enabled && mPasswordToggledVisible && mEditText != null) { - // If the toggle is no longer enabled, but we remove the PasswordTransformation - // to make the password visible, add it back - mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); - } - - // Reset the visibility tracking flag - mPasswordToggledVisible = false; - - updatePasswordToggleView(); - } - } - - /** - * Applies a tint to the the password visibility toggle drawable. Does not modify the current - * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. - * - * <p>Subsequent calls to {@link #setPasswordVisibilityToggleDrawable(Drawable)} will - * automatically mutate the drawable and apply the specified tint and tint mode using - * {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.</p> - * - * @param tintList the tint to apply, may be null to clear tint - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleTint - */ - public void setPasswordVisibilityToggleTintList(@Nullable ColorStateList tintList) { - mPasswordToggleTintList = tintList; - mHasPasswordToggleTintList = true; - applyPasswordToggleTint(); - } - - /** - * Specifies the blending mode used to apply the tint specified by - * {@link #setPasswordVisibilityToggleTintList(ColorStateList)} to the password - * visibility toggle drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.</p> - * - * @param mode the blending mode used to apply the tint, may be null to clear tint - * - * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleTintMode - */ - public void setPasswordVisibilityToggleTintMode(@Nullable PorterDuff.Mode mode) { - mPasswordToggleTintMode = mode; - mHasPasswordToggleTintMode = true; - applyPasswordToggleTint(); - } - - private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) { - if (mPasswordToggleEnabled) { - // Store the current cursor position - final int selection = mEditText.getSelectionEnd(); - - if (hasPasswordTransformation()) { - mEditText.setTransformationMethod(null); - mPasswordToggledVisible = true; - } else { - mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); - mPasswordToggledVisible = false; - } - - mPasswordToggleView.setChecked(mPasswordToggledVisible); - if (shouldSkipAnimations) { - mPasswordToggleView.jumpDrawablesToCurrentState(); - } - - // And restore the cursor position - mEditText.setSelection(selection); - } - } - - private boolean hasPasswordTransformation() { - return mEditText != null - && mEditText.getTransformationMethod() instanceof PasswordTransformationMethod; - } - - private boolean shouldShowPasswordIcon() { - return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible); - } - - private void applyPasswordToggleTint() { - if (mPasswordToggleDrawable != null - && (mHasPasswordToggleTintList || mHasPasswordToggleTintMode)) { - mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate(); - - if (mHasPasswordToggleTintList) { - DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList); - } - if (mHasPasswordToggleTintMode) { - DrawableCompat.setTintMode(mPasswordToggleDrawable, mPasswordToggleTintMode); - } - - if (mPasswordToggleView != null - && mPasswordToggleView.getDrawable() != mPasswordToggleDrawable) { - mPasswordToggleView.setImageDrawable(mPasswordToggleDrawable); - } - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - if (mHintEnabled && mEditText != null) { - final Rect rect = mTmpRect; - ViewGroupUtils.getDescendantRect(this, mEditText, rect); - - final int l = rect.left + mEditText.getCompoundPaddingLeft(); - final int r = rect.right - mEditText.getCompoundPaddingRight(); - - mCollapsingTextHelper.setExpandedBounds( - l, rect.top + mEditText.getCompoundPaddingTop(), - r, rect.bottom - mEditText.getCompoundPaddingBottom()); - - // Set the collapsed bounds to be the the full height (minus padding) to match the - // EditText's editable area - mCollapsingTextHelper.setCollapsedBounds(l, getPaddingTop(), - r, bottom - top - getPaddingBottom()); - - mCollapsingTextHelper.recalculate(); - } - } - - private void collapseHint(boolean animate) { - if (mAnimator != null && mAnimator.isRunning()) { - mAnimator.cancel(); - } - if (animate && mHintAnimationEnabled) { - animateToExpansionFraction(1f); - } else { - mCollapsingTextHelper.setExpansionFraction(1f); - } - mHintExpanded = false; - } - - @Override - protected void drawableStateChanged() { - if (mInDrawableStateChanged) { - // Some of the calls below will update the drawable state of child views. Since we're - // using addStatesFromChildren we can get into infinite recursion, hence we'll just - // exit in this instance - return; - } - - mInDrawableStateChanged = true; - - super.drawableStateChanged(); - - final int[] state = getDrawableState(); - boolean changed = false; - - // Drawable state has changed so see if we need to update the label - updateLabelState(ViewCompat.isLaidOut(this) && isEnabled()); - - updateEditTextBackground(); - - if (mCollapsingTextHelper != null) { - changed |= mCollapsingTextHelper.setState(state); - } - - if (changed) { - invalidate(); - } - - mInDrawableStateChanged = false; - } - - private void expandHint(boolean animate) { - if (mAnimator != null && mAnimator.isRunning()) { - mAnimator.cancel(); - } - if (animate && mHintAnimationEnabled) { - animateToExpansionFraction(0f); - } else { - mCollapsingTextHelper.setExpansionFraction(0f); - } - mHintExpanded = true; - } - - @VisibleForTesting - void animateToExpansionFraction(final float target) { - if (mCollapsingTextHelper.getExpansionFraction() == target) { - return; - } - if (mAnimator == null) { - mAnimator = new ValueAnimator(); - mAnimator.setInterpolator(AnimationUtils.LINEAR_INTERPOLATOR); - mAnimator.setDuration(ANIMATION_DURATION); - mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - mCollapsingTextHelper.setExpansionFraction((float) animator.getAnimatedValue()); - } - }); - } - mAnimator.setFloatValues(mCollapsingTextHelper.getExpansionFraction(), target); - mAnimator.start(); - } - - @VisibleForTesting - final boolean isHintExpanded() { - return mHintExpanded; - } - - private class TextInputAccessibilityDelegate extends AccessibilityDelegateCompat { - TextInputAccessibilityDelegate() { - } - - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - event.setClassName(TextInputLayout.class.getSimpleName()); - } - - @Override - public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(host, event); - - final CharSequence text = mCollapsingTextHelper.getText(); - if (!TextUtils.isEmpty(text)) { - event.getText().add(text); - } - } - - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setClassName(TextInputLayout.class.getSimpleName()); - - final CharSequence text = mCollapsingTextHelper.getText(); - if (!TextUtils.isEmpty(text)) { - info.setText(text); - } - if (mEditText != null) { - info.setLabelFor(mEditText); - } - final CharSequence error = mErrorView != null ? mErrorView.getText() : null; - if (!TextUtils.isEmpty(error)) { - info.setContentInvalid(true); - info.setError(error); - } - } - } - - private static boolean arrayContains(int[] array, int value) { - for (int v : array) { - if (v == value) { - return true; - } - } - return false; - } -} diff --git a/android/support/design/widget/ThemeUtils.java b/android/support/design/widget/ThemeUtils.java deleted file mode 100644 index 821dcb64..00000000 --- a/android/support/design/widget/ThemeUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.content.Context; -import android.content.res.TypedArray; - -class ThemeUtils { - - private static final int[] APPCOMPAT_CHECK_ATTRS = { - android.support.v7.appcompat.R.attr.colorPrimary - }; - - static void checkAppCompatTheme(Context context) { - TypedArray a = context.obtainStyledAttributes(APPCOMPAT_CHECK_ATTRS); - final boolean failed = !a.hasValue(0); - a.recycle(); - if (failed) { - throw new IllegalArgumentException("You need to use a Theme.AppCompat theme " - + "(or descendant) with the design library."); - } - } -} diff --git a/android/support/design/widget/ViewOffsetBehavior.java b/android/support/design/widget/ViewOffsetBehavior.java deleted file mode 100644 index 541de696..00000000 --- a/android/support/design/widget/ViewOffsetBehavior.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.content.Context; -import android.util.AttributeSet; -import android.view.View; - -/** - * Behavior will automatically sets up a {@link ViewOffsetHelper} on a {@link View}. - */ -class ViewOffsetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> { - - private ViewOffsetHelper mViewOffsetHelper; - - private int mTempTopBottomOffset = 0; - private int mTempLeftRightOffset = 0; - - public ViewOffsetBehavior() {} - - public ViewOffsetBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { - // First let lay the child out - layoutChild(parent, child, layoutDirection); - - if (mViewOffsetHelper == null) { - mViewOffsetHelper = new ViewOffsetHelper(child); - } - mViewOffsetHelper.onViewLayout(); - - if (mTempTopBottomOffset != 0) { - mViewOffsetHelper.setTopAndBottomOffset(mTempTopBottomOffset); - mTempTopBottomOffset = 0; - } - if (mTempLeftRightOffset != 0) { - mViewOffsetHelper.setLeftAndRightOffset(mTempLeftRightOffset); - mTempLeftRightOffset = 0; - } - - return true; - } - - protected void layoutChild(CoordinatorLayout parent, V child, int layoutDirection) { - // Let the parent lay it out by default - parent.onLayoutChild(child, layoutDirection); - } - - public boolean setTopAndBottomOffset(int offset) { - if (mViewOffsetHelper != null) { - return mViewOffsetHelper.setTopAndBottomOffset(offset); - } else { - mTempTopBottomOffset = offset; - } - return false; - } - - public boolean setLeftAndRightOffset(int offset) { - if (mViewOffsetHelper != null) { - return mViewOffsetHelper.setLeftAndRightOffset(offset); - } else { - mTempLeftRightOffset = offset; - } - return false; - } - - public int getTopAndBottomOffset() { - return mViewOffsetHelper != null ? mViewOffsetHelper.getTopAndBottomOffset() : 0; - } - - public int getLeftAndRightOffset() { - return mViewOffsetHelper != null ? mViewOffsetHelper.getLeftAndRightOffset() : 0; - } -}
\ No newline at end of file diff --git a/android/support/design/widget/ViewOffsetHelper.java b/android/support/design/widget/ViewOffsetHelper.java deleted file mode 100644 index 088430af..00000000 --- a/android/support/design/widget/ViewOffsetHelper.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.support.v4.view.ViewCompat; -import android.view.View; - -/** - * Utility helper for moving a {@link android.view.View} around using - * {@link android.view.View#offsetLeftAndRight(int)} and - * {@link android.view.View#offsetTopAndBottom(int)}. - * <p> - * Also the setting of absolute offsets (similar to translationX/Y), rather than additive - * offsets. - */ -class ViewOffsetHelper { - - private final View mView; - - private int mLayoutTop; - private int mLayoutLeft; - private int mOffsetTop; - private int mOffsetLeft; - - public ViewOffsetHelper(View view) { - mView = view; - } - - public void onViewLayout() { - // Now grab the intended top - mLayoutTop = mView.getTop(); - mLayoutLeft = mView.getLeft(); - - // And offset it as needed - updateOffsets(); - } - - private void updateOffsets() { - ViewCompat.offsetTopAndBottom(mView, mOffsetTop - (mView.getTop() - mLayoutTop)); - ViewCompat.offsetLeftAndRight(mView, mOffsetLeft - (mView.getLeft() - mLayoutLeft)); - } - - /** - * Set the top and bottom offset for this {@link ViewOffsetHelper}'s view. - * - * @param offset the offset in px. - * @return true if the offset has changed - */ - public boolean setTopAndBottomOffset(int offset) { - if (mOffsetTop != offset) { - mOffsetTop = offset; - updateOffsets(); - return true; - } - return false; - } - - /** - * Set the left and right offset for this {@link ViewOffsetHelper}'s view. - * - * @param offset the offset in px. - * @return true if the offset has changed - */ - public boolean setLeftAndRightOffset(int offset) { - if (mOffsetLeft != offset) { - mOffsetLeft = offset; - updateOffsets(); - return true; - } - return false; - } - - public int getTopAndBottomOffset() { - return mOffsetTop; - } - - public int getLeftAndRightOffset() { - return mOffsetLeft; - } - - public int getLayoutTop() { - return mLayoutTop; - } - - public int getLayoutLeft() { - return mLayoutLeft; - } -}
\ No newline at end of file diff --git a/android/support/design/widget/ViewUtils.java b/android/support/design/widget/ViewUtils.java deleted file mode 100644 index c09eac16..00000000 --- a/android/support/design/widget/ViewUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.graphics.PorterDuff; - -class ViewUtils { - static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) { - switch (value) { - case 3: - return PorterDuff.Mode.SRC_OVER; - case 5: - return PorterDuff.Mode.SRC_IN; - case 9: - return PorterDuff.Mode.SRC_ATOP; - case 14: - return PorterDuff.Mode.MULTIPLY; - case 15: - return PorterDuff.Mode.SCREEN; - default: - return defaultMode; - } - } - -} diff --git a/android/support/design/widget/ViewUtilsLollipop.java b/android/support/design/widget/ViewUtilsLollipop.java deleted file mode 100644 index 5927e9b8..00000000 --- a/android/support/design/widget/ViewUtilsLollipop.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.AnimatorInflater; -import android.animation.ObjectAnimator; -import android.animation.StateListAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.RequiresApi; -import android.support.design.R; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOutlineProvider; - -@RequiresApi(21) -class ViewUtilsLollipop { - - private static final int[] STATE_LIST_ANIM_ATTRS = new int[] {android.R.attr.stateListAnimator}; - - static void setBoundsViewOutlineProvider(View view) { - view.setOutlineProvider(ViewOutlineProvider.BOUNDS); - } - - static void setStateListAnimatorFromAttrs(View view, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - final Context context = view.getContext(); - final TypedArray a = context.obtainStyledAttributes(attrs, STATE_LIST_ANIM_ATTRS, - defStyleAttr, defStyleRes); - try { - if (a.hasValue(0)) { - StateListAnimator sla = AnimatorInflater.loadStateListAnimator(context, - a.getResourceId(0, 0)); - view.setStateListAnimator(sla); - } - } finally { - a.recycle(); - } - } - - /** - * Creates and sets a {@link StateListAnimator} with a custom elevation value - */ - static void setDefaultAppBarLayoutStateListAnimator(final View view, final float elevation) { - final int dur = view.getResources().getInteger(R.integer.app_bar_elevation_anim_duration); - - final StateListAnimator sla = new StateListAnimator(); - - // Enabled and collapsible, but not collapsed means not elevated - sla.addState(new int[]{android.R.attr.enabled, R.attr.state_collapsible, - -R.attr.state_collapsed}, - ObjectAnimator.ofFloat(view, "elevation", 0f).setDuration(dur)); - - // Default enabled state - sla.addState(new int[]{android.R.attr.enabled}, - ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(dur)); - - // Disabled state - sla.addState(new int[0], - ObjectAnimator.ofFloat(view, "elevation", 0).setDuration(0)); - - view.setStateListAnimator(sla); - } - -} diff --git a/android/support/design/widget/VisibilityAwareImageButton.java b/android/support/design/widget/VisibilityAwareImageButton.java deleted file mode 100644 index d7a0b13f..00000000 --- a/android/support/design/widget/VisibilityAwareImageButton.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.content.Context; -import android.util.AttributeSet; -import android.widget.ImageButton; - -class VisibilityAwareImageButton extends ImageButton { - - private int mUserSetVisibility; - - public VisibilityAwareImageButton(Context context) { - this(context, null); - } - - public VisibilityAwareImageButton(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public VisibilityAwareImageButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mUserSetVisibility = getVisibility(); - } - - @Override - public void setVisibility(int visibility) { - internalSetVisibility(visibility, true); - } - - final void internalSetVisibility(int visibility, boolean fromUser) { - super.setVisibility(visibility); - if (fromUser) { - mUserSetVisibility = visibility; - } - } - - final int getUserSetVisibility() { - return mUserSetVisibility; - } -} diff --git a/android/support/graphics/drawable/AndroidResources.java b/android/support/graphics/drawable/AndroidResources.java index 31370a2b..804c6239 100644 --- a/android/support/graphics/drawable/AndroidResources.java +++ b/android/support/graphics/drawable/AndroidResources.java @@ -89,8 +89,7 @@ class AndroidResources { public static final int[] STYLEABLE_ANIMATOR = { 0x01010141, 0x01010198, 0x010101be, 0x010101bf, - 0x010101c0, 0x010102de, 0x010102df, 0x010102e0, - 0x0111009c + 0x010101c0, 0x010102de, 0x010102df, 0x010102e0 }; public static final int STYLEABLE_ANIMATOR_INTERPOLATOR = 0; @@ -101,7 +100,6 @@ class AndroidResources { public static final int STYLEABLE_ANIMATOR_VALUE_FROM = 5; public static final int STYLEABLE_ANIMATOR_VALUE_TO = 6; public static final int STYLEABLE_ANIMATOR_VALUE_TYPE = 7; - public static final int STYLEABLE_ANIMATOR_REMOVE_BEFORE_M_RELEASE = 8; public static final int[] STYLEABLE_ANIMATOR_SET = { 0x010102e2 }; diff --git a/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java b/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java index cff61bcc..bc521cc7 100644 --- a/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java +++ b/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java @@ -118,6 +118,9 @@ import java.util.List; * <td>trimPathStart</td> * </tr> * <tr> + * <td>trimPathEnd</td> + * </tr> + * <tr> * <td>trimPathOffset</td> * </tr> * </table> diff --git a/android/support/graphics/drawable/AnimatorInflaterCompat.java b/android/support/graphics/drawable/AnimatorInflaterCompat.java index cfededb4..da522f6e 100644 --- a/android/support/graphics/drawable/AnimatorInflaterCompat.java +++ b/android/support/graphics/drawable/AnimatorInflaterCompat.java @@ -463,7 +463,6 @@ public class AnimatorInflaterCompat { // the previously sampled contours' total length. for (int i = 0; i < numPoints; ++i) { pathMeasure.getPosTan(currentDistance, position, null); - pathMeasure.getPosTan(currentDistance, position, null); mX[i] = position[0]; mY[i] = position[1]; diff --git a/android/support/media/ExifInterface.java b/android/support/media/ExifInterface.java index eea69ab1..6b437a69 100644 --- a/android/support/media/ExifInterface.java +++ b/android/support/media/ExifInterface.java @@ -23,6 +23,7 @@ import android.location.Location; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.util.Log; import android.util.Pair; @@ -3550,6 +3551,7 @@ public class ExifInterface { // Indices of Exif Ifd tag groups /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) @Retention(RetentionPolicy.SOURCE) @IntDef({IFD_TYPE_PRIMARY, IFD_TYPE_EXIF, IFD_TYPE_GPS, IFD_TYPE_INTEROPERABILITY, IFD_TYPE_THUMBNAIL, IFD_TYPE_PREVIEW, IFD_TYPE_ORF_MAKER_NOTE, @@ -4567,6 +4569,7 @@ public class ExifInterface { * @param timeStamp number of milliseconds since Jan. 1, 1970, midnight local time. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public void setDateTime(long timeStamp) { long sub = timeStamp % 1000; setAttribute(TAG_DATETIME, sFormatter.format(new Date(timeStamp))); @@ -4578,6 +4581,7 @@ public class ExifInterface { * Returns -1 if the date time information if not available. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public long getDateTime() { String dateTimeString = getAttribute(TAG_DATETIME); if (dateTimeString == null @@ -4614,6 +4618,7 @@ public class ExifInterface { * Returns -1 if the date time information if not available. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public long getGpsDateTime() { String date = getAttribute(TAG_GPS_DATESTAMP); String time = getAttribute(TAG_GPS_TIMESTAMP); diff --git a/android/support/media/tv/BasePreviewProgram.java b/android/support/media/tv/BasePreviewProgram.java index eeaa5ea1..816b1a13 100644 --- a/android/support/media/tv/BasePreviewProgram.java +++ b/android/support/media/tv/BasePreviewProgram.java @@ -15,6 +15,7 @@ */ package android.support.media.tv; +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.content.ContentValues; @@ -39,6 +40,7 @@ import java.util.TimeZone; * * @hide */ +@RestrictTo(LIBRARY) public abstract class BasePreviewProgram extends BaseProgram { /** * @hide diff --git a/android/support/media/tv/BaseProgram.java b/android/support/media/tv/BaseProgram.java index 23b5cf9c..4c7882dd 100644 --- a/android/support/media/tv/BaseProgram.java +++ b/android/support/media/tv/BaseProgram.java @@ -15,6 +15,7 @@ */ package android.support.media.tv; +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.content.ContentValues; @@ -37,6 +38,7 @@ import java.lang.annotation.RetentionPolicy; * {@link TvContractCompat}. * @hide */ +@RestrictTo(LIBRARY) public abstract class BaseProgram { /** * @hide diff --git a/android/support/media/tv/TvContractCompat.java b/android/support/media/tv/TvContractCompat.java index de4fd04f..bd03bf1b 100644 --- a/android/support/media/tv/TvContractCompat.java +++ b/android/support/media/tv/TvContractCompat.java @@ -2422,6 +2422,7 @@ public final class TvContractCompat { /** Canonical genres for TV programs. */ public static final class Genres { /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) @StringDef({ FAMILY_KIDS, SPORTS, diff --git a/android/support/multidex/MultiDex.java b/android/support/multidex/MultiDex.java index ab7f668b..2b681db0 100644 --- a/android/support/multidex/MultiDex.java +++ b/android/support/multidex/MultiDex.java @@ -28,6 +28,7 @@ import dalvik.system.DexFile; import java.io.File; import java.io.IOException; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -114,7 +115,8 @@ public final class MultiDex { new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), CODE_CACHE_SECONDARY_FOLDER_NAME, - NO_KEY_PREFIX); + NO_KEY_PREFIX, + true); } catch (Exception e) { Log.e(TAG, "MultiDex installation failure", e); @@ -171,13 +173,15 @@ public final class MultiDex { new File(instrumentationInfo.sourceDir), dataDir, instrumentationPrefix + CODE_CACHE_SECONDARY_FOLDER_NAME, - instrumentationPrefix); + instrumentationPrefix, + false); doInstallation(targetContext, new File(applicationInfo.sourceDir), dataDir, CODE_CACHE_SECONDARY_FOLDER_NAME, - NO_KEY_PREFIX); + NO_KEY_PREFIX, + false); } catch (Exception e) { Log.e(TAG, "MultiDex installation failure", e); throw new RuntimeException("MultiDex installation failed (" + e.getMessage() + ")."); @@ -192,11 +196,15 @@ public final class MultiDex { * @param dataDir data directory to use for code cache simulation. * @param secondaryFolderName name of the folder for storing extractions. * @param prefsKeyPrefix prefix of all stored preference keys. + * @param reinstallOnPatchRecoverableException if set to true, will attempt a clean extraction + * if a possibly recoverable exception occurs during classloader patching. */ private static void doInstallation(Context mainContext, File sourceApk, File dataDir, - String secondaryFolderName, String prefsKeyPrefix) throws IOException, + String secondaryFolderName, String prefsKeyPrefix, + boolean reinstallOnPatchRecoverableException) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, - InvocationTargetException, NoSuchMethodException { + InvocationTargetException, NoSuchMethodException, SecurityException, + ClassNotFoundException, InstantiationException { synchronized (installedApk) { if (installedApk.contains(sourceApk)) { return; @@ -245,9 +253,38 @@ public final class MultiDex { } File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName); - List<? extends File> files = - MultiDexExtractor.load(mainContext, sourceApk, dexDir, prefsKeyPrefix, false); - installSecondaryDexes(loader, dexDir, files); + // MultiDexExtractor is taking the file lock and keeping it until it is closed. + // Keep it open during installSecondaryDexes and through forced extraction to ensure no + // extraction or optimizing dexopt is running in parallel. + MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir); + IOException closeException = null; + try { + List<? extends File> files = + extractor.load(mainContext, prefsKeyPrefix, false); + try { + installSecondaryDexes(loader, dexDir, files); + // Some IOException causes may be fixed by a clean extraction. + } catch (IOException e) { + if (!reinstallOnPatchRecoverableException) { + throw e; + } + Log.w(TAG, "Failed to install extracted secondary dex files, retrying with " + + "forced extraction", e); + files = extractor.load(mainContext, prefsKeyPrefix, true); + installSecondaryDexes(loader, dexDir, files); + } + } finally { + try { + extractor.close(); + } catch (IOException e) { + // Delay throw of close exception to ensure we don't override some exception + // thrown during the try block. + closeException = e; + } + } + if (closeException != null) { + throw closeException; + } } } @@ -305,12 +342,13 @@ public final class MultiDex { private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, - InvocationTargetException, NoSuchMethodException, IOException { + InvocationTargetException, NoSuchMethodException, IOException, SecurityException, + ClassNotFoundException, InstantiationException { if (!files.isEmpty()) { if (Build.VERSION.SDK_INT >= 19) { V19.install(loader, files, dexDir); } else if (Build.VERSION.SDK_INT >= 14) { - V14.install(loader, files, dexDir); + V14.install(loader, files); } else { V4.install(loader, files); } @@ -460,11 +498,12 @@ public final class MultiDex { */ private static final class V19 { - private static void install(ClassLoader loader, + static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, - NoSuchFieldException, InvocationTargetException, NoSuchMethodException { + NoSuchFieldException, InvocationTargetException, NoSuchMethodException, + IOException { /* The patched class loader is expected to be a descendant of * dalvik.system.BaseDexClassLoader. We modify its * dalvik.system.DexPathList pathList field to append additional DEX @@ -500,6 +539,10 @@ public final class MultiDex { } suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions); + + IOException exception = new IOException("I/O exception during makeDexElement"); + exception.initCause(suppressedExceptions.get(0)); + throw exception; } } @@ -526,11 +569,16 @@ public final class MultiDex { */ private static final class V14 { - private static void install(ClassLoader loader, - List<? extends File> additionalClassPathEntries, - File optimizedDirectory) - throws IllegalArgumentException, IllegalAccessException, - NoSuchFieldException, InvocationTargetException, NoSuchMethodException { + private static final int EXTRACTED_SUFFIX_LENGTH = + MultiDexExtractor.EXTRACTED_SUFFIX.length(); + + private final Constructor<?> elementConstructor; + + static void install(ClassLoader loader, + List<? extends File> additionalClassPathEntries) + throws IOException, SecurityException, IllegalArgumentException, + ClassNotFoundException, NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException, NoSuchFieldException { /* The patched class loader is expected to be a descendant of * dalvik.system.BaseDexClassLoader. We modify its * dalvik.system.DexPathList pathList field to append additional DEX @@ -538,22 +586,52 @@ public final class MultiDex { */ Field pathListField = findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); - expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, - new ArrayList<File>(additionalClassPathEntries), optimizedDirectory)); + expandFieldArray(dexPathList, "dexElements", + new V14().makeDexElements(additionalClassPathEntries)); + } + + private V14() throws ClassNotFoundException, SecurityException, NoSuchMethodException { + Class<?> elementClass = Class.forName("dalvik.system.DexPathList$Element"); + elementConstructor = + elementClass.getConstructor(File.class, ZipFile.class, DexFile.class); + elementConstructor.setAccessible(true); } /** - * A wrapper around - * {@code private static final dalvik.system.DexPathList#makeDexElements}. + * An emulation of {@code private static final dalvik.system.DexPathList#makeDexElements} + * accepting only extracted secondary dex files. + * OS version is catching IOException and just logging some of them, this version is letting + * them through. */ - private static Object[] makeDexElements( - Object dexPathList, ArrayList<File> files, File optimizedDirectory) - throws IllegalAccessException, InvocationTargetException, - NoSuchMethodException { - Method makeDexElements = - findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); + private Object[] makeDexElements(List<? extends File> files) + throws IOException, SecurityException, IllegalArgumentException, + InstantiationException, IllegalAccessException, InvocationTargetException { + Object[] elements = new Object[files.size()]; + for (int i = 0; i < elements.length; i++) { + File file = files.get(i); + elements[i] = elementConstructor.newInstance( + file, + new ZipFile(file), + DexFile.loadDex(file.getPath(), optimizedPathFor(file), 0)); + } + return elements; + } - return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); + /** + * Converts a zip file path of an extracted secondary dex to an output file path for an + * associated optimized dex file. + */ + private static String optimizedPathFor(File path) { + // Any reproducible name ending with ".dex" should do but lets keep the same name + // as DexPathList.optimizedPathFor + + File optimizedDirectory = path.getParentFile(); + String fileName = path.getName(); + String optimizedFileName = + fileName.substring(0, fileName.length() - EXTRACTED_SUFFIX_LENGTH) + + MultiDexExtractor.DEX_SUFFIX; + File result = new File(optimizedDirectory, optimizedFileName); + return result.getPath(); } } @@ -561,7 +639,7 @@ public final class MultiDex { * Installer for platform versions 4 to 13. */ private static final class V4 { - private static void install(ClassLoader loader, + static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, IOException { diff --git a/android/support/multidex/MultiDexExtractor.java b/android/support/multidex/MultiDexExtractor.java index 39b6bf78..f0fd6d42 100644 --- a/android/support/multidex/MultiDexExtractor.java +++ b/android/support/multidex/MultiDexExtractor.java @@ -40,8 +40,10 @@ import java.util.zip.ZipOutputStream; /** * Exposes application secondary dex files as files in the application data * directory. + * {@link MultiDexExtractor} is taking the file lock in the dex dir on creation and release it + * during close. */ -final class MultiDexExtractor { +final class MultiDexExtractor implements Closeable { /** * Zip file containing one secondary dex file. @@ -61,10 +63,10 @@ final class MultiDexExtractor { * {@code classes3.dex}, etc. */ private static final String DEX_PREFIX = "classes"; - private static final String DEX_SUFFIX = ".dex"; + static final String DEX_SUFFIX = ".dex"; private static final String EXTRACTED_NAME_EXT = ".classes"; - private static final String EXTRACTED_SUFFIX = ".zip"; + static final String EXTRACTED_SUFFIX = ".zip"; private static final int MAX_EXTRACT_ATTEMPTS = 3; private static final String PREFS_FILE = "multidex.version"; @@ -82,6 +84,35 @@ final class MultiDexExtractor { private static final long NO_VALUE = -1L; private static final String LOCK_FILENAME = "MultiDex.lock"; + private final File sourceApk; + private final long sourceCrc; + private final File dexDir; + private final RandomAccessFile lockRaf; + private final FileChannel lockChannel; + private final FileLock cacheLock; + + MultiDexExtractor(File sourceApk, File dexDir) throws IOException { + Log.i(TAG, "MultiDexExtractor(" + sourceApk.getPath() + ", " + dexDir.getPath() + ")"); + this.sourceApk = sourceApk; + this.dexDir = dexDir; + sourceCrc = getZipCrc(sourceApk); + File lockFile = new File(dexDir, LOCK_FILENAME); + lockRaf = new RandomAccessFile(lockFile, "rw"); + try { + lockChannel = lockRaf.getChannel(); + try { + Log.i(TAG, "Blocking on lock " + lockFile.getPath()); + cacheLock = lockChannel.lock(); + } catch (IOException | RuntimeException | Error e) { + closeQuietly(lockChannel); + throw e; + } + Log.i(TAG, lockFile.getPath() + " locked"); + } catch (IOException | RuntimeException | Error e) { + closeQuietly(lockRaf); + throw e; + } + } /** * Extracts application secondary dexes into files in the application data @@ -92,74 +123,54 @@ final class MultiDexExtractor { * @throws IOException if encounters a problem while reading or writing * secondary dex files */ - static List<? extends File> load(Context context, File sourceApk, File dexDir, - String prefsKeyPrefix, - boolean forceReload) throws IOException { + List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) + throws IOException { Log.i(TAG, "MultiDexExtractor.load(" + sourceApk.getPath() + ", " + forceReload + ", " + prefsKeyPrefix + ")"); - long currentCrc = getZipCrc(sourceApk); + if (!cacheLock.isValid()) { + throw new IllegalStateException("MultiDexExtractor was closed"); + } - // Validity check and extraction must be done only while the lock file has been taken. - File lockFile = new File(dexDir, LOCK_FILENAME); - RandomAccessFile lockRaf = new RandomAccessFile(lockFile, "rw"); - FileChannel lockChannel = null; - FileLock cacheLock = null; List<ExtractedDex> files; - IOException releaseLockException = null; - try { - lockChannel = lockRaf.getChannel(); - Log.i(TAG, "Blocking on lock " + lockFile.getPath()); - cacheLock = lockChannel.lock(); - Log.i(TAG, lockFile.getPath() + " locked"); - - if (!forceReload && !isModified(context, sourceApk, currentCrc, prefsKeyPrefix)) { - try { - files = loadExistingExtractions(context, sourceApk, dexDir, prefsKeyPrefix); - } catch (IOException ioe) { - Log.w(TAG, "Failed to reload existing extracted secondary dex files," - + " falling back to fresh extraction", ioe); - files = performExtractions(sourceApk, dexDir); - putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, - files); - } - } else { - Log.i(TAG, "Detected that extraction must be performed."); - files = performExtractions(sourceApk, dexDir); - putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), currentCrc, + if (!forceReload && !isModified(context, sourceApk, sourceCrc, prefsKeyPrefix)) { + try { + files = loadExistingExtractions(context, prefsKeyPrefix); + } catch (IOException ioe) { + Log.w(TAG, "Failed to reload existing extracted secondary dex files," + + " falling back to fresh extraction", ioe); + files = performExtractions(); + putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc, files); } - } finally { - if (cacheLock != null) { - try { - cacheLock.release(); - } catch (IOException e) { - Log.e(TAG, "Failed to release lock on " + lockFile.getPath()); - // Exception while releasing the lock is bad, we want to report it, but not at - // the price of overriding any already pending exception. - releaseLockException = e; - } - } - if (lockChannel != null) { - closeQuietly(lockChannel); + } else { + if (forceReload) { + Log.i(TAG, "Forced extraction must be performed."); + } else { + Log.i(TAG, "Detected that extraction must be performed."); } - closeQuietly(lockRaf); - } - - if (releaseLockException != null) { - throw releaseLockException; + files = performExtractions(); + putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(sourceApk), sourceCrc, + files); } Log.i(TAG, "load found " + files.size() + " secondary dex files"); return files; } + @Override + public void close() throws IOException { + cacheLock.release(); + lockChannel.close(); + lockRaf.close(); + } + /** * Load previously extracted secondary dex files. Should be called only while owning the lock on * {@link #LOCK_FILENAME}. */ - private static List<ExtractedDex> loadExistingExtractions( - Context context, File sourceApk, File dexDir, + private List<ExtractedDex> loadExistingExtractions( + Context context, String prefsKeyPrefix) throws IOException { Log.i(TAG, "loading existing secondary dex files"); @@ -228,16 +239,14 @@ final class MultiDexExtractor { return computedValue; } - private static List<ExtractedDex> performExtractions(File sourceApk, File dexDir) - throws IOException { + private List<ExtractedDex> performExtractions() throws IOException { final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; - // Ensure that whatever deletions happen in prepareDexDir only happen if the zip that - // contains a secondary dex file in there is not consistent with the latest apk. Otherwise, - // multi-process race conditions can cause a crash loop where one process deletes the zip - // while another had created it. - prepareDexDir(dexDir, extractedFilePrefix); + // It is safe to fully clear the dex dir because we own the file lock so no other process is + // extracting or running optimizing dexopt. It may cause crash of already running + // applications if for whatever reason we end up extracting again over a valid extraction. + clearDexDir(); List<ExtractedDex> files = new ArrayList<ExtractedDex>(); @@ -272,9 +281,9 @@ final class MultiDexExtractor { } // Log size and crc of the extracted zip file - Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "succeeded" : "failed") + - " - length " + extractedFile.getAbsolutePath() + ": " + - extractedFile.length() + " - crc: " + extractedFile.crc); + Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "succeeded" : "failed") + + " '" + extractedFile.getAbsolutePath() + "': length " + + extractedFile.length() + " - crc: " + extractedFile.crc); if (!isExtractionSuccessful) { // Delete the extracted file extractedFile.delete(); @@ -339,19 +348,15 @@ final class MultiDexExtractor { } /** - * This removes old files. + * Clear the dex dir from all files but the lock. */ - private static void prepareDexDir(File dexDir, final String extractedFilePrefix) { - FileFilter filter = new FileFilter() { - + private void clearDexDir() { + File[] files = dexDir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { - String name = pathname.getName(); - return !(name.startsWith(extractedFilePrefix) - || name.equals(LOCK_FILENAME)); + return !pathname.getName().equals(LOCK_FILENAME); } - }; - File[] files = dexDir.listFiles(filter); + }); if (files == null) { Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); return; diff --git a/android/support/transition/TransitionSet.java b/android/support/transition/TransitionSet.java index 404245a1..24075bbf 100644 --- a/android/support/transition/TransitionSet.java +++ b/android/support/transition/TransitionSet.java @@ -51,7 +51,7 @@ import java.util.ArrayList; * transition on the affected view targets:</p> * <pre> * <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" - * android:ordering="sequential"> + * android:transitionOrdering="sequential"> * <fade/> * <changeBounds/> * </transitionSet> @@ -561,6 +561,15 @@ public class TransitionSet extends Transition { } @Override + public void setPropagation(TransitionPropagation propagation) { + super.setPropagation(propagation); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).setPropagation(propagation); + } + } + + @Override public void setEpicenterCallback(EpicenterCallback epicenterCallback) { super.setEpicenterCallback(epicenterCallback); int numTransitions = mTransitions.size(); diff --git a/android/support/transition/TransitionSetTest.java b/android/support/transition/TransitionSetTest.java index aec9ecb2..d82cd498 100644 --- a/android/support/transition/TransitionSetTest.java +++ b/android/support/transition/TransitionSetTest.java @@ -120,4 +120,12 @@ public class TransitionSetTest extends BaseTest { assertThat(mTransition.getTargetTypes(), hasSize(0)); } + @Test + public void testSetPropagation() { + final TransitionPropagation propagation = new SidePropagation(); + mTransitionSet.setPropagation(propagation); + assertThat(mTransitionSet.getPropagation(), is(propagation)); + assertThat(mTransition.getPropagation(), is(propagation)); + } + } diff --git a/android/support/v13/view/DragAndDropPermissionsCompat.java b/android/support/v13/view/DragAndDropPermissionsCompat.java index 13ed2038..5fe61da9 100644 --- a/android/support/v13/view/DragAndDropPermissionsCompat.java +++ b/android/support/v13/view/DragAndDropPermissionsCompat.java @@ -20,57 +20,16 @@ import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.app.Activity; import android.os.Build; -import android.support.annotation.RequiresApi; +import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; import android.view.DragAndDropPermissions; import android.view.DragEvent; /** - * Helper for accessing features in {@link android.view.DragAndDropPermissions} - * introduced after API level 13 in a backwards compatible fashion. + * Helper for accessing features in {@link android.view.DragAndDropPermissions} a backwards + * compatible fashion. */ public final class DragAndDropPermissionsCompat { - - interface DragAndDropPermissionsCompatImpl { - Object request(Activity activity, DragEvent dragEvent); - void release(Object dragAndDropPermissions); - } - - static class BaseDragAndDropPermissionsCompatImpl implements DragAndDropPermissionsCompatImpl { - @Override - public Object request(Activity activity, DragEvent dragEvent) { - return null; - } - - @Override - public void release(Object dragAndDropPermissions) { - // no-op - } - } - - @RequiresApi(24) - static class Api24DragAndDropPermissionsCompatImpl - extends BaseDragAndDropPermissionsCompatImpl { - @Override - public Object request(Activity activity, DragEvent dragEvent) { - return activity.requestDragAndDropPermissions(dragEvent); - } - - @Override - public void release(Object dragAndDropPermissions) { - ((DragAndDropPermissions) dragAndDropPermissions).release(); - } - } - - private static DragAndDropPermissionsCompatImpl IMPL; - static { - if (Build.VERSION.SDK_INT >= 24) { - IMPL = new Api24DragAndDropPermissionsCompatImpl(); - } else { - IMPL = new BaseDragAndDropPermissionsCompatImpl(); - } - } - private Object mDragAndDropPermissions; private DragAndDropPermissionsCompat(Object dragAndDropPermissions) { @@ -79,18 +38,24 @@ public final class DragAndDropPermissionsCompat { /** @hide */ @RestrictTo(LIBRARY_GROUP) + @Nullable public static DragAndDropPermissionsCompat request(Activity activity, DragEvent dragEvent) { - Object dragAndDropPermissions = IMPL.request(activity, dragEvent); - if (dragAndDropPermissions != null) { - return new DragAndDropPermissionsCompat(dragAndDropPermissions); + if (Build.VERSION.SDK_INT >= 24) { + DragAndDropPermissions dragAndDropPermissions = + activity.requestDragAndDropPermissions(dragEvent); + if (dragAndDropPermissions != null) { + return new DragAndDropPermissionsCompat(dragAndDropPermissions); + } } return null; } - /* + /** * Revoke the permission grant explicitly. */ public void release() { - IMPL.release(mDragAndDropPermissions); + if (Build.VERSION.SDK_INT >= 24) { + ((DragAndDropPermissions) mDragAndDropPermissions).release(); + } } } diff --git a/android/support/v13/view/DragStartHelper.java b/android/support/v13/view/DragStartHelper.java index 85bc2f36..f8aed922 100644 --- a/android/support/v13/view/DragStartHelper.java +++ b/android/support/v13/view/DragStartHelper.java @@ -69,8 +69,8 @@ import android.view.View; * </pre> */ public class DragStartHelper { - final private View mView; - final private OnDragStartListener mListener; + private final View mView; + private final OnDragStartListener mListener; private int mLastTouchX, mLastTouchY; private boolean mDragging; diff --git a/android/support/v13/view/inputmethod/EditorInfoCompat.java b/android/support/v13/view/inputmethod/EditorInfoCompat.java index 92743c25..309877dc 100644 --- a/android/support/v13/view/inputmethod/EditorInfoCompat.java +++ b/android/support/v13/view/inputmethod/EditorInfoCompat.java @@ -16,7 +16,6 @@ package android.support.v13.view.inputmethod; -import android.support.annotation.RequiresApi; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; @@ -24,8 +23,7 @@ import android.support.annotation.Nullable; import android.view.inputmethod.EditorInfo; /** - * Helper for accessing features in {@link EditorInfo} introduced after API level 13 in a backwards - * compatible fashion. + * Helper for accessing features in {@link EditorInfo} in a backwards compatible fashion. */ public final class EditorInfoCompat { @@ -69,63 +67,10 @@ public final class EditorInfoCompat { */ public static final int IME_FLAG_FORCE_ASCII = 0x80000000; - private interface EditorInfoCompatImpl { - void setContentMimeTypes(@NonNull EditorInfo editorInfo, - @Nullable String[] contentMimeTypes); - @NonNull - String[] getContentMimeTypes(@NonNull EditorInfo editorInfo); - } - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private static final class EditorInfoCompatBaseImpl implements EditorInfoCompatImpl { - private static String CONTENT_MIME_TYPES_KEY = - "android.support.v13.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES"; - - @Override - public void setContentMimeTypes(@NonNull EditorInfo editorInfo, - @Nullable String[] contentMimeTypes) { - if (editorInfo.extras == null) { - editorInfo.extras = new Bundle(); - } - editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_KEY, contentMimeTypes); - } - - @NonNull - @Override - public String[] getContentMimeTypes(@NonNull EditorInfo editorInfo) { - if (editorInfo.extras == null) { - return EMPTY_STRING_ARRAY; - } - String[] result = editorInfo.extras.getStringArray(CONTENT_MIME_TYPES_KEY); - return result != null ? result : EMPTY_STRING_ARRAY; - } - } - - @RequiresApi(25) - private static final class EditorInfoCompatApi25Impl implements EditorInfoCompatImpl { - @Override - public void setContentMimeTypes(@NonNull EditorInfo editorInfo, - @Nullable String[] contentMimeTypes) { - editorInfo.contentMimeTypes = contentMimeTypes; - } - - @NonNull - @Override - public String[] getContentMimeTypes(@NonNull EditorInfo editorInfo) { - final String[] result = editorInfo.contentMimeTypes; - return result != null ? result : EMPTY_STRING_ARRAY; - } - } - - private static final EditorInfoCompatImpl IMPL; - static { - if (Build.VERSION.SDK_INT >= 25) { - IMPL = new EditorInfoCompatApi25Impl(); - } else { - IMPL = new EditorInfoCompatBaseImpl(); - } - } + private static final String CONTENT_MIME_TYPES_KEY = + "android.support.v13.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES"; /** * Sets MIME types that can be accepted by the target editor if the IME calls @@ -140,7 +85,14 @@ public final class EditorInfoCompat { */ public static void setContentMimeTypes(@NonNull EditorInfo editorInfo, @Nullable String[] contentMimeTypes) { - IMPL.setContentMimeTypes(editorInfo, contentMimeTypes); + if (Build.VERSION.SDK_INT >= 25) { + editorInfo.contentMimeTypes = contentMimeTypes; + } else { + if (editorInfo.extras == null) { + editorInfo.extras = new Bundle(); + } + editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_KEY, contentMimeTypes); + } } /** @@ -155,7 +107,16 @@ public final class EditorInfoCompat { */ @NonNull public static String[] getContentMimeTypes(EditorInfo editorInfo) { - return IMPL.getContentMimeTypes(editorInfo); + if (Build.VERSION.SDK_INT >= 25) { + final String[] result = editorInfo.contentMimeTypes; + return result != null ? result : EMPTY_STRING_ARRAY; + } else { + if (editorInfo.extras == null) { + return EMPTY_STRING_ARRAY; + } + String[] result = editorInfo.extras.getStringArray(CONTENT_MIME_TYPES_KEY); + return result != null ? result : EMPTY_STRING_ARRAY; + } } } diff --git a/android/support/v13/view/inputmethod/InputConnectionCompat.java b/android/support/v13/view/inputmethod/InputConnectionCompat.java index 5999575b..d77389bf 100644 --- a/android/support/v13/view/inputmethod/InputConnectionCompat.java +++ b/android/support/v13/view/inputmethod/InputConnectionCompat.java @@ -16,7 +16,6 @@ package android.support.v13.view.inputmethod; -import android.support.annotation.RequiresApi; import android.content.ClipDescription; import android.net.Uri; import android.os.Build; @@ -36,138 +35,50 @@ import android.view.inputmethod.InputContentInfo; */ public final class InputConnectionCompat { - private interface InputConnectionCompatImpl { - boolean commitContent(@NonNull InputConnection inputConnection, - @NonNull InputContentInfoCompat inputContentInfo, int flags, @Nullable Bundle opts); - - @NonNull - InputConnection createWrapper(@NonNull InputConnection ic, - @NonNull EditorInfo editorInfo, @NonNull OnCommitContentListener callback); - } - - static final class InputContentInfoCompatBaseImpl implements InputConnectionCompatImpl { - - private static String COMMIT_CONTENT_ACTION = - "android.support.v13.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT"; - private static String COMMIT_CONTENT_CONTENT_URI_KEY = - "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_URI"; - private static String COMMIT_CONTENT_DESCRIPTION_KEY = - "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION"; - private static String COMMIT_CONTENT_LINK_URI_KEY = - "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI"; - private static String COMMIT_CONTENT_OPTS_KEY = - "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_OPTS"; - private static String COMMIT_CONTENT_FLAGS_KEY = - "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS"; - private static String COMMIT_CONTENT_RESULT_RECEIVER = - "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER"; - - @Override - public boolean commitContent(@NonNull InputConnection inputConnection, - @NonNull InputContentInfoCompat inputContentInfo, int flags, - @Nullable Bundle opts) { - final Bundle params = new Bundle(); - params.putParcelable(COMMIT_CONTENT_CONTENT_URI_KEY, inputContentInfo.getContentUri()); - params.putParcelable(COMMIT_CONTENT_DESCRIPTION_KEY, inputContentInfo.getDescription()); - params.putParcelable(COMMIT_CONTENT_LINK_URI_KEY, inputContentInfo.getLinkUri()); - params.putInt(COMMIT_CONTENT_FLAGS_KEY, flags); - params.putParcelable(COMMIT_CONTENT_OPTS_KEY, opts); - // TODO: Support COMMIT_CONTENT_RESULT_RECEIVER. - return inputConnection.performPrivateCommand(COMMIT_CONTENT_ACTION, params); + private static final String COMMIT_CONTENT_ACTION = + "android.support.v13.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT"; + private static final String COMMIT_CONTENT_CONTENT_URI_KEY = + "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_URI"; + private static final String COMMIT_CONTENT_DESCRIPTION_KEY = + "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION"; + private static final String COMMIT_CONTENT_LINK_URI_KEY = + "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI"; + private static final String COMMIT_CONTENT_OPTS_KEY = + "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_OPTS"; + private static final String COMMIT_CONTENT_FLAGS_KEY = + "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS"; + private static final String COMMIT_CONTENT_RESULT_RECEIVER = + "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER"; + + static boolean handlePerformPrivateCommand( + @Nullable String action, + @NonNull Bundle data, + @NonNull OnCommitContentListener onCommitContentListener) { + if (!TextUtils.equals(COMMIT_CONTENT_ACTION, action)) { + return false; } - - @NonNull - @Override - public InputConnection createWrapper(@NonNull InputConnection ic, - @NonNull EditorInfo editorInfo, - @NonNull OnCommitContentListener onCommitContentListener) { - String[] contentMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo); - if (contentMimeTypes.length == 0) { - return ic; - } - final OnCommitContentListener listener = onCommitContentListener; - return new InputConnectionWrapper(ic, false /* mutable */) { - @Override - public boolean performPrivateCommand(String action, Bundle data) { - if (InputContentInfoCompatBaseImpl.handlePerformPrivateCommand(action, data, - listener)) { - return true; - } - return super.performPrivateCommand(action, data); - } - }; + if (data == null) { + return false; } - - static boolean handlePerformPrivateCommand( - @Nullable String action, - @NonNull Bundle data, - @NonNull OnCommitContentListener onCommitContentListener) { - if (!TextUtils.equals(COMMIT_CONTENT_ACTION, action)) { - return false; + ResultReceiver resultReceiver = null; + boolean result = false; + try { + resultReceiver = data.getParcelable(COMMIT_CONTENT_RESULT_RECEIVER); + final Uri contentUri = data.getParcelable(COMMIT_CONTENT_CONTENT_URI_KEY); + final ClipDescription description = data.getParcelable( + COMMIT_CONTENT_DESCRIPTION_KEY); + final Uri linkUri = data.getParcelable(COMMIT_CONTENT_LINK_URI_KEY); + final int flags = data.getInt(COMMIT_CONTENT_FLAGS_KEY); + final Bundle opts = data.getParcelable(COMMIT_CONTENT_OPTS_KEY); + final InputContentInfoCompat inputContentInfo = + new InputContentInfoCompat(contentUri, description, linkUri); + result = onCommitContentListener.onCommitContent(inputContentInfo, flags, opts); + } finally { + if (resultReceiver != null) { + resultReceiver.send(result ? 1 : 0, null); } - if (data == null) { - return false; - } - ResultReceiver resultReceiver = null; - boolean result = false; - try { - resultReceiver = data.getParcelable(COMMIT_CONTENT_RESULT_RECEIVER); - final Uri contentUri = data.getParcelable(COMMIT_CONTENT_CONTENT_URI_KEY); - final ClipDescription description = data.getParcelable( - COMMIT_CONTENT_DESCRIPTION_KEY); - final Uri linkUri = data.getParcelable(COMMIT_CONTENT_LINK_URI_KEY); - final int flags = data.getInt(COMMIT_CONTENT_FLAGS_KEY); - final Bundle opts = data.getParcelable(COMMIT_CONTENT_OPTS_KEY); - final InputContentInfoCompat inputContentInfo = - new InputContentInfoCompat(contentUri, description, linkUri); - result = onCommitContentListener.onCommitContent(inputContentInfo, flags, opts); - } finally { - if (resultReceiver != null) { - resultReceiver.send(result ? 1 : 0, null); - } - } - return result; - } - } - - @RequiresApi(25) - private static final class InputContentInfoCompatApi25Impl - implements InputConnectionCompatImpl { - @Override - public boolean commitContent(@NonNull InputConnection inputConnection, - @NonNull InputContentInfoCompat inputContentInfo, int flags, - @Nullable Bundle opts) { - return inputConnection.commitContent((InputContentInfo) inputContentInfo.unwrap(), - flags, opts); - } - - @Nullable - @Override - public InputConnection createWrapper( - @Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, - @Nullable OnCommitContentListener onCommitContentListener) { - final OnCommitContentListener listener = onCommitContentListener; - return new InputConnectionWrapper(inputConnection, false /* mutable */) { - @Override - public boolean commitContent(InputContentInfo inputContentInfo, int flags, - Bundle opts) { - if (listener.onCommitContent(InputContentInfoCompat.wrap(inputContentInfo), - flags, opts)) { - return true; - } - return super.commitContent(inputContentInfo, flags, opts); - } - }; - } - } - - private static final InputConnectionCompatImpl IMPL; - static { - if (Build.VERSION.SDK_INT >= 25) { - IMPL = new InputContentInfoCompatApi25Impl(); - } else { - IMPL = new InputContentInfoCompatBaseImpl(); } + return result; } /** @@ -196,7 +107,19 @@ public final class InputConnectionCompat { return false; } - return IMPL.commitContent(inputConnection, inputContentInfo, flags, opts); + if (Build.VERSION.SDK_INT >= 25) { + return inputConnection.commitContent( + (InputContentInfo) inputContentInfo.unwrap(), flags, opts); + } else { + final Bundle params = new Bundle(); + params.putParcelable(COMMIT_CONTENT_CONTENT_URI_KEY, inputContentInfo.getContentUri()); + params.putParcelable(COMMIT_CONTENT_DESCRIPTION_KEY, inputContentInfo.getDescription()); + params.putParcelable(COMMIT_CONTENT_LINK_URI_KEY, inputContentInfo.getLinkUri()); + params.putInt(COMMIT_CONTENT_FLAGS_KEY, flags); + params.putParcelable(COMMIT_CONTENT_OPTS_KEY, opts); + // TODO: Support COMMIT_CONTENT_RESULT_RECEIVER. + return inputConnection.performPrivateCommand(COMMIT_CONTENT_ACTION, params); + } } /** @@ -276,7 +199,35 @@ public final class InputConnectionCompat { if (onCommitContentListener == null) { throw new IllegalArgumentException("onCommitContentListener must be non-null"); } - return IMPL.createWrapper(inputConnection, editorInfo, onCommitContentListener); + if (Build.VERSION.SDK_INT >= 25) { + final OnCommitContentListener listener = onCommitContentListener; + return new InputConnectionWrapper(inputConnection, false /* mutable */) { + @Override + public boolean commitContent(InputContentInfo inputContentInfo, int flags, + Bundle opts) { + if (listener.onCommitContent(InputContentInfoCompat.wrap(inputContentInfo), + flags, opts)) { + return true; + } + return super.commitContent(inputContentInfo, flags, opts); + } + }; + } else { + String[] contentMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo); + if (contentMimeTypes.length == 0) { + return inputConnection; + } + final OnCommitContentListener listener = onCommitContentListener; + return new InputConnectionWrapper(inputConnection, false /* mutable */) { + @Override + public boolean performPrivateCommand(String action, Bundle data) { + if (InputConnectionCompat.handlePerformPrivateCommand(action, data, listener)) { + return true; + } + return super.performPrivateCommand(action, data); + } + }; + } } } diff --git a/android/support/v14/preference/EditTextPreferenceDialogFragment.java b/android/support/v14/preference/EditTextPreferenceDialogFragment.java index 3ee58725..23b88288 100644 --- a/android/support/v14/preference/EditTextPreferenceDialogFragment.java +++ b/android/support/v14/preference/EditTextPreferenceDialogFragment.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package android.support.v14.preference; @@ -65,8 +65,8 @@ public class EditTextPreferenceDialogFragment extends PreferenceDialogFragment { mEditText = (EditText) view.findViewById(android.R.id.edit); if (mEditText == null) { - throw new IllegalStateException("Dialog view must contain an EditText with id" + - " @android:id/edit"); + throw new IllegalStateException("Dialog view must contain an EditText with id" + + " @android:id/edit"); } mEditText.setText(mText); diff --git a/android/support/v14/preference/ListPreferenceDialogFragment.java b/android/support/v14/preference/ListPreferenceDialogFragment.java index 6119071b..5374cd53 100644 --- a/android/support/v14/preference/ListPreferenceDialogFragment.java +++ b/android/support/v14/preference/ListPreferenceDialogFragment.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package android.support.v14.preference; diff --git a/android/support/v14/preference/MultiSelectListPreference.java b/android/support/v14/preference/MultiSelectListPreference.java index f34b7dcd..16351fec 100644 --- a/android/support/v14/preference/MultiSelectListPreference.java +++ b/android/support/v14/preference/MultiSelectListPreference.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package android.support.v14.preference; @@ -23,6 +23,7 @@ import android.os.Parcelable; import android.support.annotation.ArrayRes; import android.support.annotation.NonNull; import android.support.v4.content.res.TypedArrayUtils; +import android.support.v7.preference.R; import android.support.v7.preference.internal.AbstractMultiSelectListPreference; import android.util.AttributeSet; @@ -35,7 +36,7 @@ import java.util.Set; * a dialog. * <p> * This preference will store a set of strings into the SharedPreferences. - * This set will contain one or more values from the + * This set will contain one or more mValues from the * {@link #setEntryValues(CharSequence[])} array. * * @attr name android:entries @@ -51,16 +52,16 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes(attrs, - android.support.v7.preference.R.styleable.MultiSelectListPreference, defStyleAttr, + R.styleable.MultiSelectListPreference, defStyleAttr, defStyleRes); mEntries = TypedArrayUtils.getTextArray(a, - android.support.v7.preference.R.styleable.MultiSelectListPreference_entries, - android.support.v7.preference.R.styleable.MultiSelectListPreference_android_entries); + R.styleable.MultiSelectListPreference_entries, + R.styleable.MultiSelectListPreference_android_entries); mEntryValues = TypedArrayUtils.getTextArray(a, - android.support.v7.preference.R.styleable.MultiSelectListPreference_entryValues, - android.support.v7.preference.R.styleable.MultiSelectListPreference_android_entryValues); + R.styleable.MultiSelectListPreference_entryValues, + R.styleable.MultiSelectListPreference_android_entryValues); a.recycle(); } @@ -71,7 +72,7 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference public MultiSelectListPreference(Context context, AttributeSet attrs) { this(context, attrs, TypedArrayUtils.getAttr(context, - android.support.v7.preference.R.attr.dialogPreferenceStyle, + R.attr.dialogPreferenceStyle, android.R.attr.dialogPreferenceStyle)); } @@ -116,7 +117,7 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference * entries is selected. If a user clicks on the second item in entries, the * second item in this array will be saved to the preference. * - * @param entryValues The array to be used as values to save for the preference. + * @param entryValues The array to be used as mValues to save for the preference. */ public void setEntryValues(CharSequence[] entryValues) { mEntryValues = entryValues; @@ -124,16 +125,16 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference /** * @see #setEntryValues(CharSequence[]) - * @param entryValuesResId The entry values array as a resource. + * @param entryValuesResId The entry mValues array as a resource. */ public void setEntryValues(@ArrayRes int entryValuesResId) { setEntryValues(getContext().getResources().getTextArray(entryValuesResId)); } /** - * Returns the array of values to be saved for the preference. + * Returns the array of mValues to be saved for the preference. * - * @return The array of values. + * @return The array of mValues. */ @Override public CharSequence[] getEntryValues() { @@ -144,7 +145,7 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference * Sets the value of the key. This should contain entries in * {@link #getEntryValues()}. * - * @param values The values to set for the key. + * @param values The mValues to set for the key. */ @Override public void setValues(Set<String> values) { @@ -163,7 +164,7 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference } /** - * Returns the index of the given value (in the entry values array). + * Returns the index of the given value (in the entry mValues array). * * @param value The value whose index should be returned. * @return The index of the value, or -1 if not found. @@ -219,7 +220,7 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference } final SavedState myState = new SavedState(superState); - myState.values = getValues(); + myState.mValues = getValues(); return myState; } @@ -233,31 +234,31 @@ public class MultiSelectListPreference extends AbstractMultiSelectListPreference SavedState myState = (SavedState) state; super.onRestoreInstanceState(myState.getSuperState()); - setValues(myState.values); + setValues(myState.mValues); } private static class SavedState extends BaseSavedState { - Set<String> values; + Set<String> mValues; - public SavedState(Parcel source) { + SavedState(Parcel source) { super(source); final int size = source.readInt(); - values = new HashSet<>(); + mValues = new HashSet<>(); String[] strings = new String[size]; source.readStringArray(strings); - Collections.addAll(values, strings); + Collections.addAll(mValues, strings); } - public SavedState(Parcelable superState) { + SavedState(Parcelable superState) { super(superState); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeInt(values.size()); - dest.writeStringArray(values.toArray(new String[values.size()])); + dest.writeInt(mValues.size()); + dest.writeStringArray(mValues.toArray(new String[mValues.size()])); } public static final Parcelable.Creator<SavedState> CREATOR = diff --git a/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java b/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java index 81925833..db81644a 100644 --- a/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java +++ b/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package android.support.v14.preference; @@ -60,8 +60,8 @@ public class MultiSelectListPreferenceDialogFragment extends PreferenceDialogFra if (preference.getEntries() == null || preference.getEntryValues() == null) { throw new IllegalStateException( - "MultiSelectListPreference requires an entries array and " + - "an entryValues array."); + "MultiSelectListPreference requires an entries array and " + + "an entryValues array."); } mNewValues.clear(); diff --git a/android/support/v14/preference/PreferenceDialogFragment.java b/android/support/v14/preference/PreferenceDialogFragment.java index e7b9f403..a4ae4a96 100644 --- a/android/support/v14/preference/PreferenceDialogFragment.java +++ b/android/support/v14/preference/PreferenceDialogFragment.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package android.support.v14.preference; @@ -78,8 +78,8 @@ public abstract class PreferenceDialogFragment extends DialogFragment implements final Fragment rawFragment = getTargetFragment(); if (!(rawFragment instanceof DialogPreference.TargetFragment)) { - throw new IllegalStateException("Target fragment must implement TargetFragment" + - " interface"); + throw new IllegalStateException("Target fragment must implement TargetFragment" + + " interface"); } final DialogPreference.TargetFragment fragment = @@ -132,9 +132,9 @@ public abstract class PreferenceDialogFragment extends DialogFragment implements } } + @NonNull @Override - public @NonNull - Dialog onCreateDialog(Bundle savedInstanceState) { + public Dialog onCreateDialog(Bundle savedInstanceState) { final Context context = getActivity(); mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; diff --git a/android/support/v14/preference/PreferenceFragment.java b/android/support/v14/preference/PreferenceFragment.java index 24210505..406465f1 100644 --- a/android/support/v14/preference/PreferenceFragment.java +++ b/android/support/v14/preference/PreferenceFragment.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package android.support.v14.preference; @@ -44,6 +44,7 @@ import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceRecyclerViewAccessibilityDelegate; import android.support.v7.preference.PreferenceScreen; import android.support.v7.preference.PreferenceViewHolder; +import android.support.v7.preference.R; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.TypedValue; @@ -145,7 +146,7 @@ public abstract class PreferenceFragment extends Fragment implements private final DividerDecoration mDividerDecoration = new DividerDecoration(); private static final int MSG_BIND_PREFERENCES = 1; - private Handler mHandler = new Handler() { + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -157,7 +158,7 @@ public abstract class PreferenceFragment extends Fragment implements } }; - final private Runnable mRequestFocus = new Runnable() { + private final Runnable mRequestFocus = new Runnable() { @Override public void run() { mList.focusableViewAvailable(mList); @@ -484,7 +485,7 @@ public abstract class PreferenceFragment extends Fragment implements handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment()) .onPreferenceStartFragment(this, preference); } - if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback){ + if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback) { handled = ((OnPreferenceStartFragmentCallback) getActivity()) .onPreferenceStartFragment(this, preference); } @@ -656,8 +657,8 @@ public abstract class PreferenceFragment extends Fragment implements } else if (preference instanceof MultiSelectListPreference) { f = MultiSelectListPreferenceDialogFragment.newInstance(preference.getKey()); } else { - throw new IllegalArgumentException("Tried to display dialog for unknown " + - "preference type. Did you forget to override onDisplayPreferenceDialog()?"); + throw new IllegalArgumentException("Tried to display dialog for unknown " + + "preference type. Did you forget to override onDisplayPreferenceDialog()?"); } f.setTargetFragment(this, 0); f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); @@ -686,8 +687,7 @@ public abstract class PreferenceFragment extends Fragment implements @Override public void run() { final RecyclerView.Adapter adapter = mList.getAdapter(); - if (!(adapter instanceof - PreferenceGroup.PreferencePositionCallback)) { + if (!(adapter instanceof PreferenceGroup.PreferencePositionCallback)) { if (adapter != null) { throw new IllegalStateException("Adapter must implement " + "PreferencePositionCallback"); @@ -726,7 +726,7 @@ public abstract class PreferenceFragment extends Fragment implements private final Preference mPreference; private final String mKey; - public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list, + ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list, Preference preference, String key) { mAdapter = adapter; mList = list; diff --git a/android/support/v14/preference/SwitchPreference.java b/android/support/v14/preference/SwitchPreference.java index eae20b8d..197de4e1 100644 --- a/android/support/v14/preference/SwitchPreference.java +++ b/android/support/v14/preference/SwitchPreference.java @@ -1,18 +1,18 @@ /* -* 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 -*/ + * 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.v14.preference; @@ -24,6 +24,7 @@ import android.support.annotation.RestrictTo; import android.support.v4.content.res.TypedArrayUtils; import android.support.v7.preference.AndroidResources; import android.support.v7.preference.PreferenceViewHolder; +import android.support.v7.preference.R; import android.support.v7.preference.TwoStatePreference; import android.util.AttributeSet; import android.view.View; diff --git a/android/support/v17/leanback/app/PlaybackFragment.java b/android/support/v17/leanback/app/PlaybackFragment.java index e2e6be48..dc59e0eb 100644 --- a/android/support/v17/leanback/app/PlaybackFragment.java +++ b/android/support/v17/leanback/app/PlaybackFragment.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.v17.leanback.R; import android.support.v17.leanback.animation.LogAccelerateInterpolator; import android.support.v17.leanback.animation.LogDecelerateInterpolator; @@ -106,6 +107,7 @@ public class PlaybackFragment extends Fragment { * Resets the focus on the button in the middle of control row. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public void resetFocus() { ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView() .findViewHolderForAdapterPosition(0); @@ -185,6 +187,7 @@ public class PlaybackFragment extends Fragment { * @hide * @deprecated use {@link PlaybackSupportFragment} */ + @RestrictTo(RestrictTo.Scope.LIBRARY) @Deprecated public static class OnFadeCompleteListener { public void onFadeInComplete() { @@ -366,6 +369,7 @@ public class PlaybackFragment extends Fragment { * Sets the listener to be called when fade in or out has completed. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public void setFadeCompleteListener(OnFadeCompleteListener listener) { mFadeCompleteListener = listener; } @@ -374,6 +378,7 @@ public class PlaybackFragment extends Fragment { * Returns the listener to be called when fade in or out has completed. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public OnFadeCompleteListener getFadeCompleteListener() { return mFadeCompleteListener; } diff --git a/android/support/v17/leanback/app/PlaybackSupportFragment.java b/android/support/v17/leanback/app/PlaybackSupportFragment.java index a8741aba..ee17e84d 100644 --- a/android/support/v17/leanback/app/PlaybackSupportFragment.java +++ b/android/support/v17/leanback/app/PlaybackSupportFragment.java @@ -26,6 +26,7 @@ import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.v17.leanback.R; import android.support.v17.leanback.animation.LogAccelerateInterpolator; import android.support.v17.leanback.animation.LogDecelerateInterpolator; @@ -101,6 +102,7 @@ public class PlaybackSupportFragment extends Fragment { * Resets the focus on the button in the middle of control row. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public void resetFocus() { ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView() .findViewHolderForAdapterPosition(0); @@ -179,6 +181,7 @@ public class PlaybackSupportFragment extends Fragment { * completion events. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public static class OnFadeCompleteListener { public void onFadeInComplete() { } @@ -359,6 +362,7 @@ public class PlaybackSupportFragment extends Fragment { * Sets the listener to be called when fade in or out has completed. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public void setFadeCompleteListener(OnFadeCompleteListener listener) { mFadeCompleteListener = listener; } @@ -367,6 +371,7 @@ public class PlaybackSupportFragment extends Fragment { * Returns the listener to be called when fade in or out has completed. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public OnFadeCompleteListener getFadeCompleteListener() { return mFadeCompleteListener; } diff --git a/android/support/v17/leanback/media/PlaybackControlGlue.java b/android/support/v17/leanback/media/PlaybackControlGlue.java index 5bf6cc17..0a788f6e 100644 --- a/android/support/v17/leanback/media/PlaybackControlGlue.java +++ b/android/support/v17/leanback/media/PlaybackControlGlue.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; +import android.support.annotation.RestrictTo; import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter; import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.ArrayObjectAdapter; @@ -356,6 +357,7 @@ public abstract class PlaybackControlGlue extends PlaybackGlue /** * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) protected SparseArrayObjectAdapter createPrimaryActionsAdapter( PresenterSelector presenterSelector) { SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector); diff --git a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java index 4aa9bf66..b81f979b 100644 --- a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java +++ b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java @@ -365,7 +365,7 @@ public class PlaybackTransportControlGlue<T extends PlayerAdapter> @Override public void onSeekFinished(boolean cancelled) { if (!cancelled) { - if (mLastUserPosition > 0) { + if (mLastUserPosition >= 0) { seekTo(mLastUserPosition); } } else { diff --git a/android/support/v17/leanback/transition/TransitionEpicenterCallback.java b/android/support/v17/leanback/transition/TransitionEpicenterCallback.java index ec7f84cf..bb8e686a 100644 --- a/android/support/v17/leanback/transition/TransitionEpicenterCallback.java +++ b/android/support/v17/leanback/transition/TransitionEpicenterCallback.java @@ -14,11 +14,13 @@ package android.support.v17.leanback.transition; import android.graphics.Rect; +import android.support.annotation.RestrictTo; /** * Class to get the epicenter of Transition. * @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY) public abstract class TransitionEpicenterCallback { /** @@ -31,4 +33,3 @@ public abstract class TransitionEpicenterCallback { */ public abstract Rect onGetEpicenter(Object transition); } - diff --git a/android/support/v17/leanback/util/MathUtil.java b/android/support/v17/leanback/util/MathUtil.java index 487188de..bf74e405 100644 --- a/android/support/v17/leanback/util/MathUtil.java +++ b/android/support/v17/leanback/util/MathUtil.java @@ -13,10 +13,13 @@ */ package android.support.v17.leanback.util; +import android.support.annotation.RestrictTo; + /** * Math Utilities for leanback library. * @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY) public final class MathUtil { private MathUtil() { diff --git a/android/support/v17/leanback/widget/DetailsParallaxDrawable.java b/android/support/v17/leanback/widget/DetailsParallaxDrawable.java index 37e3480c..1eea7974 100644 --- a/android/support/v17/leanback/widget/DetailsParallaxDrawable.java +++ b/android/support/v17/leanback/widget/DetailsParallaxDrawable.java @@ -22,6 +22,7 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.ColorInt; +import android.support.annotation.RestrictTo; import android.support.v17.leanback.R; import android.support.v17.leanback.graphics.CompositeDrawable; import android.support.v17.leanback.graphics.FitWidthBitmapDrawable; @@ -56,6 +57,7 @@ import android.util.TypedValue; * </li> * @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY) public class DetailsParallaxDrawable extends CompositeDrawable { private Drawable mBottomDrawable; diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java index 613198fd..810cb3bf 100644 --- a/android/support/v17/leanback/widget/GridLayoutManager.java +++ b/android/support/v17/leanback/widget/GridLayoutManager.java @@ -2645,8 +2645,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mPrimaryScrollExtra = primaryScrollExtra; View view = findViewByPosition(position); // scrollToView() is based on Adapter position. Only call scrollToView() when item - // is still valid. - if (view != null && getAdapterPositionByView(view) == position) { + // is still valid and no layout is requested, otherwise defer to next layout pass. + if (!mBaseGridView.isLayoutRequested() + && view != null && getAdapterPositionByView(view) == position) { mFlag |= PF_IN_SELECTION; scrollToView(view, smooth); mFlag &= ~PF_IN_SELECTION; diff --git a/android/support/v17/leanback/widget/MediaRowFocusView.java b/android/support/v17/leanback/widget/MediaRowFocusView.java index 1418a2a4..471f64e1 100644 --- a/android/support/v17/leanback/widget/MediaRowFocusView.java +++ b/android/support/v17/leanback/widget/MediaRowFocusView.java @@ -17,14 +17,16 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; +import android.support.annotation.RestrictTo; +import android.support.v17.leanback.R; import android.util.AttributeSet; import android.view.View; -import android.support.v17.leanback.R; /** * Creates a view for a media item row in a playlist * @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY) class MediaRowFocusView extends View { private final Paint mPaint; diff --git a/android/support/v17/leanback/widget/ParallaxEffect.java b/android/support/v17/leanback/widget/ParallaxEffect.java index 5c06e29b..e1af7626 100644 --- a/android/support/v17/leanback/widget/ParallaxEffect.java +++ b/android/support/v17/leanback/widget/ParallaxEffect.java @@ -17,6 +17,7 @@ package android.support.v17.leanback.widget; import android.animation.PropertyValuesHolder; +import android.support.annotation.RestrictTo; import android.support.v17.leanback.widget.Parallax.FloatProperty; import android.support.v17.leanback.widget.Parallax.FloatPropertyMarkerValue; import android.support.v17.leanback.widget.Parallax.IntProperty; @@ -70,6 +71,7 @@ public abstract class ParallaxEffect { * @return A list of Float objects that represents weight associated with each variable range. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public final List<Float> getWeights() { return mWeights; } @@ -96,6 +98,7 @@ public abstract class ParallaxEffect { * range. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public final void setWeights(float... weights) { for (float weight : weights) { if (weight <= 0) { @@ -121,6 +124,7 @@ public abstract class ParallaxEffect { * @return This ParallaxEffect object, allowing calls to methods in this class to be chained. * @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY) public final ParallaxEffect weights(float... weights) { setWeights(weights); return this; diff --git a/android/support/v17/leanback/widget/VideoSurfaceView.java b/android/support/v17/leanback/widget/VideoSurfaceView.java index 29d778c0..d42a60d6 100644 --- a/android/support/v17/leanback/widget/VideoSurfaceView.java +++ b/android/support/v17/leanback/widget/VideoSurfaceView.java @@ -17,6 +17,7 @@ package android.support.v17.leanback.widget; import android.content.Context; +import android.support.annotation.RestrictTo; import android.util.AttributeSet; import android.view.SurfaceView; @@ -26,6 +27,7 @@ import android.view.SurfaceView; * This class disables setTransitionVisibility() to avoid the problem. * @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY) public class VideoSurfaceView extends SurfaceView { public VideoSurfaceView(Context context) { diff --git a/android/support/v4/app/ActivityCompat.java b/android/support/v4/app/ActivityCompat.java index 9d15be1d..333871a0 100644 --- a/android/support/v4/app/ActivityCompat.java +++ b/android/support/v4/app/ActivityCompat.java @@ -29,6 +29,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; +import android.support.annotation.IdRes; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -340,6 +341,31 @@ public class ActivityCompat extends ContextCompat { } /** + * Finds a view that was identified by the {@code android:id} XML attribute that was processed + * in {@link Activity#onCreate}, or throws an IllegalArgumentException if the ID is invalid, or + * there is no matching view in the hierarchy. + * <p> + * <strong>Note:</strong> In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see Activity#findViewById(int) + * @see android.support.v4.view.ViewCompat#requireViewById(View, int) + */ + @NonNull + public static <T extends View> T requireViewById(@NonNull Activity activity, @IdRes int id) { + // TODO: use and link to Activity#requireViewById() directly, once available + T view = activity.findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Activity"); + } + return view; + } + + /** * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, * android.view.View, String)} was used to start an Activity, <var>callback</var> * will be called to handle shared elements on the <i>launched</i> Activity. This requires @@ -538,6 +564,7 @@ public class ActivityCompat extends ContextCompat { * URIs. {@code null} if no content URIs are associated with the event or if permissions could * not be granted. */ + @Nullable public static DragAndDropPermissionsCompat requestDragAndDropPermissions(Activity activity, DragEvent dragEvent) { return DragAndDropPermissionsCompat.request(activity, dragEvent); diff --git a/android/support/v4/app/Fragment.java b/android/support/v4/app/Fragment.java index 5b560cd2..f3c73ae5 100644 --- a/android/support/v4/app/Fragment.java +++ b/android/support/v4/app/Fragment.java @@ -575,7 +575,6 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener /** * Return the {@link Context} this fragment is currently associated with. */ - @Nullable public Context getContext() { return mHost == null ? null : mHost.getContext(); } @@ -585,7 +584,6 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener * May return {@code null} if the fragment is associated with a {@link Context} * instead. */ - @Nullable final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } @@ -594,7 +592,6 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener * Return the host object of this fragment. May return {@code null} if the fragment * isn't currently being hosted. */ - @Nullable final public Object getHost() { return mHost == null ? null : mHost.onGetHost(); } @@ -655,7 +652,6 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener * <p>If this Fragment is a child of another Fragment, the FragmentManager * returned here will be the parent's {@link #getChildFragmentManager()}. */ - @Nullable final public FragmentManager getFragmentManager() { return mFragmentManager; } @@ -864,6 +860,12 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener } mUserVisibleHint = isVisibleToUser; mDeferStart = mState < STARTED && !isVisibleToUser; + if (mSavedFragmentState != null) { + // Ensure that if the user visible hint is set before the Fragment has + // restored its state that we don't lose the new value + mSavedFragmentState.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, + mUserVisibleHint); + } } /** diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java index 78161a87..e3f56849 100644 --- a/android/support/v4/app/FragmentActivity.java +++ b/android/support/v4/app/FragmentActivity.java @@ -273,6 +273,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + mFragments.noteStateNotSaved(); mFragments.dispatchConfigurationChanged(newConfig); } diff --git a/android/support/v4/app/LoaderManager.java b/android/support/v4/app/LoaderManager.java index 521b2184..32e211a5 100644 --- a/android/support/v4/app/LoaderManager.java +++ b/android/support/v4/app/LoaderManager.java @@ -16,8 +16,11 @@ package android.support.v4.app; -import android.app.Activity; import android.os.Bundle; +import android.os.Looper; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.content.Loader; import android.support.v4.util.DebugUtils; import android.support.v4.util.SparseArrayCompat; @@ -44,11 +47,15 @@ public abstract class LoaderManager { /** * Instantiate and return a new Loader for the given ID. * + * <p>This will always be called from the process's main thread. + * * @param id The ID whose loader is to be created. * @param args Any arguments supplied by the caller. * @return Return a new Loader instance that is ready to start loading. */ - public Loader<D> onCreateLoader(int id, Bundle args); + @MainThread + @NonNull + Loader<D> onCreateLoader(int id, @Nullable Bundle args); /** * Called when a previously created loader has finished its load. Note @@ -86,19 +93,25 @@ public abstract class LoaderManager { * method so that the old Cursor is not closed. * </ul> * + * <p>This will always be called from the process's main thread. + * * @param loader The Loader that has finished. * @param data The data generated by the Loader. */ - public void onLoadFinished(Loader<D> loader, D data); + @MainThread + void onLoadFinished(@NonNull Loader<D> loader, D data); /** * Called when a previously created loader is being reset, and thus * making its data unavailable. The application should at this point * remove any references it has to the Loader's data. * + * <p>This will always be called from the process's main thread. + * * @param loader The Loader that is being reset. */ - public void onLoaderReset(Loader<D> loader); + @MainThread + void onLoaderReset(@NonNull Loader<D> loader); } /** @@ -115,6 +128,8 @@ public abstract class LoaderManager { * be called immediately (inside of this function), so you must be prepared * for this to happen. * + * <p>Must be called from the process's main thread. + * * @param id A unique identifier for this loader. Can be whatever you want. * Identifiers are scoped to a particular LoaderManager instance. * @param args Optional arguments to supply to the loader at construction. @@ -123,8 +138,10 @@ public abstract class LoaderManager { * @param callback Interface the LoaderManager will call to report about * changes in the state of the loader. Required. */ - public abstract <D> Loader<D> initLoader(int id, Bundle args, - LoaderManager.LoaderCallbacks<D> callback); + @MainThread + @NonNull + public abstract <D> Loader<D> initLoader(int id, @Nullable Bundle args, + @NonNull LoaderManager.LoaderCallbacks<D> callback); /** * Starts a new or restarts an existing {@link android.content.Loader} in @@ -135,27 +152,35 @@ public abstract class LoaderManager { * its work. The callback will be delivered before the old loader * is destroyed. * + * <p>Must be called from the process's main thread. + * * @param id A unique identifier for this loader. Can be whatever you want. * Identifiers are scoped to a particular LoaderManager instance. * @param args Optional arguments to supply to the loader at construction. * @param callback Interface the LoaderManager will call to report about * changes in the state of the loader. Required. */ - public abstract <D> Loader<D> restartLoader(int id, Bundle args, - LoaderManager.LoaderCallbacks<D> callback); + @MainThread + @NonNull + public abstract <D> Loader<D> restartLoader(int id, @Nullable Bundle args, + @NonNull LoaderManager.LoaderCallbacks<D> callback); /** * Stops and removes the loader with the given ID. If this loader * had previously reported data to the client through * {@link LoaderCallbacks#onLoadFinished(Loader, Object)}, a call * will be made to {@link LoaderCallbacks#onLoaderReset(Loader)}. + * + * <p>Must be called from the process's main thread. */ + @MainThread public abstract void destroyLoader(int id); /** * Return the Loader with the given id or null if no matching Loader * is found. */ + @Nullable public abstract <D> Loader<D> getLoader(int id); /** @@ -378,7 +403,7 @@ class LoaderManagerImpl extends LoaderManager { } @Override - public void onLoadCanceled(Loader<Object> loader) { + public void onLoadCanceled(@NonNull Loader<Object> loader) { if (DEBUG) Log.v(TAG, "onLoadCanceled: " + this); if (mDestroyed) { @@ -407,7 +432,7 @@ class LoaderManagerImpl extends LoaderManager { } @Override - public void onLoadComplete(Loader<Object> loader, Object data) { + public void onLoadComplete(@NonNull Loader<Object> loader, Object data) { if (DEBUG) Log.v(TAG, "onLoadComplete: " + this); if (mDestroyed) { @@ -563,36 +588,18 @@ class LoaderManagerImpl extends LoaderManager { } } - /** - * Call to initialize a particular ID with a Loader. If this ID already - * has a Loader associated with it, it is left unchanged and any previous - * callbacks replaced with the newly provided ones. If there is not currently - * a Loader for the ID, a new one is created and started. - * - * <p>This function should generally be used when a component is initializing, - * to ensure that a Loader it relies on is created. This allows it to re-use - * an existing Loader's data if there already is one, so that for example - * when an {@link Activity} is re-created after a configuration change it - * does not need to re-create its loaders. - * - * <p>Note that in the case where an existing Loader is re-used, the - * <var>args</var> given here <em>will be ignored</em> because you will - * continue using the previous Loader. - * - * @param id A unique (to this LoaderManager instance) identifier under - * which to manage the new Loader. - * @param args Optional arguments that will be propagated to - * {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}. - * @param callback Interface implementing management of this Loader. Required. - * Its onCreateLoader() method will be called while inside of the function to - * instantiate the Loader object. - */ + @MainThread + @NonNull @Override @SuppressWarnings("unchecked") - public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { + public <D> Loader<D> initLoader(int id, @Nullable Bundle args, + @NonNull LoaderManager.LoaderCallbacks<D> callback) { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } + if (Looper.getMainLooper() != Looper.myLooper()) { + throw new IllegalStateException("initLoader must be called on the main thread"); + } LoaderInfo info = mLoaders.get(id); @@ -615,35 +622,18 @@ class LoaderManagerImpl extends LoaderManager { return (Loader<D>)info.mLoader; } - /** - * Call to re-create the Loader associated with a particular ID. If there - * is currently a Loader associated with this ID, it will be - * canceled/stopped/destroyed as appropriate. A new Loader with the given - * arguments will be created and its data delivered to you once available. - * - * <p>This function does some throttling of Loaders. If too many Loaders - * have been created for the given ID but not yet generated their data, - * new calls to this function will create and return a new Loader but not - * actually start it until some previous loaders have completed. - * - * <p>After calling this function, any previous Loaders associated with - * this ID will be considered invalid, and you will receive no further - * data updates from them. - * - * @param id A unique (to this LoaderManager instance) identifier under - * which to manage the new Loader. - * @param args Optional arguments that will be propagated to - * {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int, Bundle) LoaderCallbacks.onCreateLoader()}. - * @param callback Interface implementing management of this Loader. Required. - * Its onCreateLoader() method will be called while inside of the function to - * instantiate the Loader object. - */ + @MainThread + @NonNull @Override @SuppressWarnings("unchecked") - public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { + public <D> Loader<D> restartLoader(int id, @Nullable Bundle args, + @NonNull LoaderManager.LoaderCallbacks<D> callback) { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } + if (Looper.getMainLooper() != Looper.myLooper()) { + throw new IllegalStateException("restartLoader must be called on the main thread"); + } LoaderInfo info = mLoaders.get(id); if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args); @@ -701,18 +691,15 @@ class LoaderManagerImpl extends LoaderManager { return (Loader<D>)info.mLoader; } - /** - * Rip down, tear apart, shred to pieces a current Loader ID. After returning - * from this function, any Loader objects associated with this ID are - * destroyed. Any data associated with them is destroyed. You better not - * be using it when you do this. - * @param id Identifier of the Loader to be destroyed. - */ + @MainThread @Override public void destroyLoader(int id) { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } + if (Looper.getMainLooper() != Looper.myLooper()) { + throw new IllegalStateException("destroyLoader must be called on the main thread"); + } if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id); int idx = mLoaders.indexOfKey(id); @@ -732,10 +719,7 @@ class LoaderManagerImpl extends LoaderManager { } } - /** - * Return the most recent Loader object associated with the - * given ID. - */ + @Nullable @Override @SuppressWarnings("unchecked") public <D> Loader<D> getLoader(int id) { diff --git a/android/support/v4/app/NotificationCompat.java b/android/support/v4/app/NotificationCompat.java index 6f74e18c..9d71ad1f 100644 --- a/android/support/v4/app/NotificationCompat.java +++ b/android/support/v4/app/NotificationCompat.java @@ -453,6 +453,7 @@ public class NotificationCompat { public @interface StreamType {} /** @hide */ + @RestrictTo(LIBRARY_GROUP) @Retention(SOURCE) @IntDef({VISIBILITY_PUBLIC, VISIBILITY_PRIVATE, VISIBILITY_SECRET}) public @interface NotificationVisibility {} @@ -2114,8 +2115,16 @@ public class NotificationCompat { /** * Sets the title to be displayed on this conversation. May be set to {@code null}. - * @param conversationTitle Title displayed for this conversation. - * @return this object for method chaining. + * + * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your + * application's target version is less than {@link Build.VERSION_CODES#P}, setting a + * conversation title to a non-null value will make {@link #isGroupConversation()} return + * {@code true} and passing {@code null} will make it return {@code false}. In + * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} + * to set group conversation status. + * + * @param conversationTitle Title displayed for this conversation + * @return this object for method chaining */ public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { mConversationTitle = conversationTitle; @@ -2185,9 +2194,27 @@ public class NotificationCompat { } /** - * Returns {@code true} if this notification represents a group conversation. + * Returns {@code true} if this notification represents a group conversation, otherwise + * {@code false}. + * + * <p> If the application that generated this {@link MessagingStyle} targets an SDK version + * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or + * not the conversation title is set; returning {@code true} if the conversation title is + * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, + * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for + * named, non-group conversations. + * + * @see #setConversationTitle(CharSequence) */ public boolean isGroupConversation() { + // When target SDK version is < P, a non-null conversation title dictates if this is + // as group conversation. + if (mBuilder != null + && mBuilder.mContext.getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.P) { + return mConversationTitle != null; + } + return mIsGroupConversation; } @@ -2769,6 +2796,66 @@ public class NotificationCompat { * to attach actions. */ public static class Action { + /** + * {@link SemanticAction}: No semantic action defined. + */ + public static final int SEMANTIC_ACTION_NONE = 0; + + /** + * {@link SemanticAction}: Reply to a conversation, chat, group, or wherever replies + * may be appropriate. + */ + public static final int SEMANTIC_ACTION_REPLY = 1; + + /** + * {@link SemanticAction}: Mark content as read. + */ + public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; + + /** + * {@link SemanticAction}: Mark content as unread. + */ + public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; + + /** + * {@link SemanticAction}: Delete the content associated with the notification. This + * could mean deleting an email, message, etc. + */ + public static final int SEMANTIC_ACTION_DELETE = 4; + + /** + * {@link SemanticAction}: Archive the content associated with the notification. This + * could mean archiving an email, message, etc. + */ + public static final int SEMANTIC_ACTION_ARCHIVE = 5; + + /** + * {@link SemanticAction}: Mute the content associated with the notification. This could + * mean silencing a conversation or currently playing media. + */ + public static final int SEMANTIC_ACTION_MUTE = 6; + + /** + * {@link SemanticAction}: Unmute the content associated with the notification. This could + * mean un-silencing a conversation or currently playing media. + */ + public static final int SEMANTIC_ACTION_UNMUTE = 7; + + /** + * {@link SemanticAction}: Mark content with a thumbs up. + */ + public static final int SEMANTIC_ACTION_THUMBS_UP = 8; + + /** + * {@link SemanticAction}: Mark content with a thumbs down. + */ + public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; + + static final String EXTRA_SHOWS_USER_INTERFACE = + "android.support.action.showsUserInterface"; + + static final String EXTRA_SEMANTIC_ACTION = "android.support.action.semanticAction"; + final Bundle mExtras; private final RemoteInput[] mRemoteInputs; @@ -2785,6 +2872,9 @@ public class NotificationCompat { private final RemoteInput[] mDataOnlyRemoteInputs; private boolean mAllowGeneratedReplies; + private boolean mShowsUserInterface = true; + + private final @SemanticAction int mSemanticAction; /** * Small icon representing the action. @@ -2801,12 +2891,13 @@ public class NotificationCompat { public PendingIntent actionIntent; public Action(int icon, CharSequence title, PendingIntent intent) { - this(icon, title, intent, new Bundle(), null, null, true); + this(icon, title, intent, new Bundle(), null, null, true, SEMANTIC_ACTION_NONE, true); } Action(int icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs, RemoteInput[] dataOnlyRemoteInputs, - boolean allowGeneratedReplies) { + boolean allowGeneratedReplies, @SemanticAction int semanticAction, + boolean showsUserInterface) { this.icon = icon; this.title = NotificationCompat.Builder.limitCharSequenceLength(title); this.actionIntent = intent; @@ -2814,6 +2905,8 @@ public class NotificationCompat { this.mRemoteInputs = remoteInputs; this.mDataOnlyRemoteInputs = dataOnlyRemoteInputs; this.mAllowGeneratedReplies = allowGeneratedReplies; + this.mSemanticAction = semanticAction; + this.mShowsUserInterface = showsUserInterface; } public int getIcon() { @@ -2853,6 +2946,17 @@ public class NotificationCompat { } /** + * Returns the {@link SemanticAction} associated with this {@link Action}. A + * {@link SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do + * (eg. reply, mark as read, delete, etc). + * + * @see SemanticAction + */ + public @SemanticAction int getSemanticAction() { + return mSemanticAction; + } + + /** * Get the list of inputs to be collected from the user that ONLY accept data when this * action is sent. These remote inputs are guaranteed to return true on a call to * {@link RemoteInput#isDataOnly}. @@ -2867,6 +2971,14 @@ public class NotificationCompat { } /** + * Return whether or not triggering this {@link Action}'s {@link PendingIntent} will open a + * user interface. + */ + public boolean getShowsUserInterface() { + return mShowsUserInterface; + } + + /** * Builder class for {@link Action} objects. */ public static final class Builder { @@ -2876,6 +2988,8 @@ public class NotificationCompat { private boolean mAllowGeneratedReplies = true; private final Bundle mExtras; private ArrayList<RemoteInput> mRemoteInputs; + private @SemanticAction int mSemanticAction; + private boolean mShowsUserInterface = true; /** * Construct a new builder for {@link Action} object. @@ -2884,7 +2998,7 @@ public class NotificationCompat { * @param intent the {@link PendingIntent} to fire when users trigger this action */ public Builder(int icon, CharSequence title, PendingIntent intent) { - this(icon, title, intent, new Bundle(), null, true); + this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, true); } /** @@ -2894,11 +3008,13 @@ public class NotificationCompat { */ public Builder(Action action) { this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras), - action.getRemoteInputs(), action.getAllowGeneratedReplies()); + action.getRemoteInputs(), action.getAllowGeneratedReplies(), + action.getSemanticAction(), action.mShowsUserInterface); } private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras, - RemoteInput[] remoteInputs, boolean allowGeneratedReplies) { + RemoteInput[] remoteInputs, boolean allowGeneratedReplies, + @SemanticAction int semanticAction, boolean showsUserInterface) { mIcon = icon; mTitle = NotificationCompat.Builder.limitCharSequenceLength(title); mIntent = intent; @@ -2906,6 +3022,8 @@ public class NotificationCompat { mRemoteInputs = remoteInputs == null ? null : new ArrayList<>( Arrays.asList(remoteInputs)); mAllowGeneratedReplies = allowGeneratedReplies; + mSemanticAction = semanticAction; + mShowsUserInterface = showsUserInterface; } /** @@ -2961,6 +3079,32 @@ public class NotificationCompat { } /** + * Sets the {@link SemanticAction} for this {@link Action}. A {@link SemanticAction} + * denotes what an {@link Action}'s {@link PendingIntent} will do (eg. reply, mark + * as read, delete, etc). + * @param semanticAction a {@link SemanticAction} defined within {@link Action} with + * {@code SEMANTIC_ACTION_} prefixes + * @return this object for method chaining + */ + public Builder setSemanticAction(@SemanticAction int semanticAction) { + mSemanticAction = semanticAction; + return this; + } + + /** + * Set whether or not this {@link Action}'s {@link PendingIntent} will open a user + * interface. + * @param showsUserInterface {@code true} if this {@link Action}'s {@link PendingIntent} + * will open a user interface, otherwise {@code false} + * @return this object for method chaining + * The default value is {@code true} + */ + public Builder setShowsUserInterface(boolean showsUserInterface) { + mShowsUserInterface = showsUserInterface; + return this; + } + + /** * Apply an extender to this action builder. Extenders may be used to add * metadata or change options on this builder. */ @@ -2991,7 +3135,8 @@ public class NotificationCompat { RemoteInput[] textInputsArr = textInputs.isEmpty() ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, - dataOnlyInputsArr, mAllowGeneratedReplies); + dataOnlyInputsArr, mAllowGeneratedReplies, mSemanticAction, + mShowsUserInterface); } } @@ -3251,6 +3396,27 @@ public class NotificationCompat { return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; } } + + /** + * Provides meaning to an {@link Action} that hints at what the associated + * {@link PendingIntent} will do. For example, an {@link Action} with a + * {@link PendingIntent} that replies to a text message notification may have the + * {@link #SEMANTIC_ACTION_REPLY} {@link SemanticAction} set within it. + */ + @IntDef({ + SEMANTIC_ACTION_NONE, + SEMANTIC_ACTION_REPLY, + SEMANTIC_ACTION_MARK_AS_READ, + SEMANTIC_ACTION_MARK_AS_UNREAD, + SEMANTIC_ACTION_DELETE, + SEMANTIC_ACTION_ARCHIVE, + SEMANTIC_ACTION_MUTE, + SEMANTIC_ACTION_UNMUTE, + SEMANTIC_ACTION_THUMBS_UP, + SEMANTIC_ACTION_THUMBS_DOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SemanticAction {} } @@ -4651,8 +4817,21 @@ public class NotificationCompat { allowGeneratedReplies = action.getExtras().getBoolean( NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES); } + + final boolean showsUserInterface = + action.getExtras().getBoolean(Action.EXTRA_SHOWS_USER_INTERFACE, true); + + final @Action.SemanticAction int semanticAction; + if (Build.VERSION.SDK_INT >= 28) { + semanticAction = action.getSemanticAction(); + } else { + semanticAction = action.getExtras().getInt( + Action.EXTRA_SEMANTIC_ACTION, Action.SEMANTIC_ACTION_NONE); + } + return new Action(action.icon, action.title, action.actionIntent, - action.getExtras(), remoteInputs, null, allowGeneratedReplies); + action.getExtras(), remoteInputs, null, allowGeneratedReplies, + semanticAction, showsUserInterface); } /** diff --git a/android/support/v4/app/NotificationCompatBuilder.java b/android/support/v4/app/NotificationCompatBuilder.java index db775a55..e5fb4f95 100644 --- a/android/support/v4/app/NotificationCompatBuilder.java +++ b/android/support/v4/app/NotificationCompatBuilder.java @@ -248,6 +248,15 @@ class NotificationCompatBuilder implements NotificationBuilderWithBuilderAccesso if (Build.VERSION.SDK_INT >= 24) { actionBuilder.setAllowGeneratedReplies(action.getAllowGeneratedReplies()); } + + actionExtras.putInt(NotificationCompat.Action.EXTRA_SEMANTIC_ACTION, + action.getSemanticAction()); + if (Build.VERSION.SDK_INT >= 28) { + actionBuilder.setSemanticAction(action.getSemanticAction()); + } + + actionExtras.putBoolean(NotificationCompat.Action.EXTRA_SHOWS_USER_INTERFACE, + action.getShowsUserInterface()); actionBuilder.addExtras(actionExtras); mBuilder.addAction(actionBuilder.build()); } else if (Build.VERSION.SDK_INT >= 16) { diff --git a/android/support/v4/app/NotificationCompatJellybean.java b/android/support/v4/app/NotificationCompatJellybean.java index 9cdd2e95..82f89412 100644 --- a/android/support/v4/app/NotificationCompatJellybean.java +++ b/android/support/v4/app/NotificationCompatJellybean.java @@ -129,7 +129,8 @@ class NotificationCompatJellybean { allowGeneratedReplies = extras.getBoolean(EXTRA_ALLOW_GENERATED_REPLIES); } return new NotificationCompat.Action(icon, title, actionIntent, extras, remoteInputs, - dataOnlyRemoteInputs, allowGeneratedReplies); + dataOnlyRemoteInputs, allowGeneratedReplies, + NotificationCompat.Action.SEMANTIC_ACTION_NONE, true); } public static Bundle writeActionAndGetExtras( @@ -236,7 +237,9 @@ class NotificationCompatJellybean { bundle.getBundle(KEY_EXTRAS), fromBundleArray(getBundleArrayFromBundle(bundle, KEY_REMOTE_INPUTS)), fromBundleArray(getBundleArrayFromBundle(bundle, KEY_DATA_ONLY_REMOTE_INPUTS)), - allowGeneratedReplies); + allowGeneratedReplies, + NotificationCompat.Action.SEMANTIC_ACTION_NONE, + true); } static Bundle getBundleForAction(NotificationCompat.Action action) { diff --git a/android/support/v4/app/NotificationManagerCompat.java b/android/support/v4/app/NotificationManagerCompat.java index 07fcb6c7..7099cb9b 100644 --- a/android/support/v4/app/NotificationManagerCompat.java +++ b/android/support/v4/app/NotificationManagerCompat.java @@ -190,7 +190,7 @@ public final class NotificationManagerCompat { * @param id the ID of the notification * @param notification the notification to post to the system */ - public void notify(int id, Notification notification) { + public void notify(int id, @NonNull Notification notification) { notify(null, id, notification); } diff --git a/android/support/v4/content/FileProvider.java b/android/support/v4/content/FileProvider.java index 8599911a..16164be9 100644 --- a/android/support/v4/content/FileProvider.java +++ b/android/support/v4/content/FileProvider.java @@ -29,6 +29,7 @@ import android.content.res.XmlResourceParser; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; +import android.os.Build; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.OpenableColumns; @@ -177,6 +178,17 @@ import java.util.Map; * subdirectory is the same as the value returned by * {@link Context#getExternalCacheDir() Context.getExternalCacheDir()}. * </dd> + * <dt> + * <pre class="prettyprint"> + *<external-media-path name="<i>name</i>" path="<i>path</i>" /> + *</pre> + * </dt> + * <dd> + * Represents files in the root of your app's external media area. The root path of this + * subdirectory is the same as the value returned by the first result of + * {@link Context#getExternalMediaDirs() Context.getExternalMediaDirs()}. + * <p><strong>Note:</strong> this directory is only available on API 21+ devices.</p> + * </dd> * </dl> * <p> * These child elements all use the same attributes: @@ -336,6 +348,7 @@ public class FileProvider extends ContentProvider { private static final String TAG_EXTERNAL = "external-path"; private static final String TAG_EXTERNAL_FILES = "external-files-path"; private static final String TAG_EXTERNAL_CACHE = "external-cache-path"; + private static final String TAG_EXTERNAL_MEDIA = "external-media-path"; private static final String ATTR_NAME = "name"; private static final String ATTR_PATH = "path"; @@ -622,6 +635,12 @@ public class FileProvider extends ContentProvider { if (externalCacheDirs.length > 0) { target = externalCacheDirs[0]; } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && TAG_EXTERNAL_MEDIA.equals(tag)) { + File[] externalMediaDirs = context.getExternalMediaDirs(); + if (externalMediaDirs.length > 0) { + target = externalMediaDirs[0]; + } } if (target != null) { diff --git a/android/support/v4/content/Loader.java b/android/support/v4/content/Loader.java index 2ac10d73..431964d2 100644 --- a/android/support/v4/content/Loader.java +++ b/android/support/v4/content/Loader.java @@ -19,6 +19,7 @@ package android.support.v4.content; import android.content.Context; import android.database.ContentObserver; import android.os.Handler; +import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.DebugUtils; @@ -123,6 +124,7 @@ public class Loader<D> { * * @param data the result of the load */ + @MainThread public void deliverResult(@Nullable D data) { if (mListener != null) { mListener.onLoadComplete(this, data); @@ -135,6 +137,7 @@ public class Loader<D> { * * Must be called from the process's main thread. */ + @MainThread public void deliverCancellation() { if (mOnLoadCanceledListener != null) { mOnLoadCanceledListener.onLoadCanceled(this); @@ -163,6 +166,7 @@ public class Loader<D> { * * <p>Must be called from the process's main thread. */ + @MainThread public void registerListener(int id, @NonNull OnLoadCompleteListener<D> listener) { if (mListener != null) { throw new IllegalStateException("There is already a listener registered"); @@ -176,6 +180,7 @@ public class Loader<D> { * * Must be called from the process's main thread. */ + @MainThread public void unregisterListener(@NonNull OnLoadCompleteListener<D> listener) { if (mListener == null) { throw new IllegalStateException("No listener register"); @@ -195,6 +200,7 @@ public class Loader<D> { * * @param listener The listener to register. */ + @MainThread public void registerOnLoadCanceledListener(@NonNull OnLoadCanceledListener<D> listener) { if (mOnLoadCanceledListener != null) { throw new IllegalStateException("There is already a listener registered"); @@ -210,6 +216,7 @@ public class Loader<D> { * * @param listener The listener to unregister. */ + @MainThread public void unregisterOnLoadCanceledListener(@NonNull OnLoadCanceledListener<D> listener) { if (mOnLoadCanceledListener == null) { throw new IllegalStateException("No listener register"); @@ -268,6 +275,7 @@ public class Loader<D> { * * <p>Must be called from the process's main thread. */ + @MainThread public final void startLoading() { mStarted = true; mReset = false; @@ -279,7 +287,9 @@ public class Loader<D> { * Subclasses must implement this to take care of loading their data, * as per {@link #startLoading()}. This is not called by clients directly, * but as a result of a call to {@link #startLoading()}. + * This will always be called from the process's main thread. */ + @MainThread protected void onStartLoading() { } @@ -301,6 +311,7 @@ public class Loader<D> { * is still running and the {@link OnLoadCanceledListener} will be called * when the task completes. */ + @MainThread public boolean cancelLoad() { return onCancelLoad(); } @@ -316,6 +327,7 @@ public class Loader<D> { * is still running and the {@link OnLoadCanceledListener} will be called * when the task completes. */ + @MainThread protected boolean onCancelLoad() { return false; } @@ -328,6 +340,7 @@ public class Loader<D> { * * <p>Must be called from the process's main thread. */ + @MainThread public void forceLoad() { onForceLoad(); } @@ -336,6 +349,7 @@ public class Loader<D> { * Subclasses must implement this to take care of requests to {@link #forceLoad()}. * This will always be called from the process's main thread. */ + @MainThread protected void onForceLoad() { } @@ -359,6 +373,7 @@ public class Loader<D> { * * <p>Must be called from the process's main thread. */ + @MainThread public void stopLoading() { mStarted = false; onStopLoading(); @@ -370,6 +385,7 @@ public class Loader<D> { * but as a result of a call to {@link #stopLoading()}. * This will always be called from the process's main thread. */ + @MainThread protected void onStopLoading() { } @@ -383,12 +399,15 @@ public class Loader<D> { * Tell the Loader that it is being abandoned. This is called prior * to {@link #reset} to have it retain its current data but not report * any new data. + * + * <p>Must be called from the process's main thread. */ + @MainThread public void abandon() { mAbandoned = true; onAbandon(); } - + /** * Subclasses implement this to take care of being abandoned. This is * an optional intermediate state prior to {@link #onReset()} -- it means that @@ -397,10 +416,12 @@ public class Loader<D> { * loader <em>must</em> keep its last reported data valid until the final * {@link #onReset()} happens. You can retrieve the current abandoned * state with {@link #isAbandoned}. + * This will always be called from the process's main thread. */ + @MainThread protected void onAbandon() { } - + /** * This function will normally be called for you automatically by * {@link android.support.v4.app.LoaderManager} when destroying a Loader. When using @@ -419,6 +440,7 @@ public class Loader<D> { * * <p>Must be called from the process's main thread. */ + @MainThread public void reset() { onReset(); mReset = true; @@ -434,6 +456,7 @@ public class Loader<D> { * but as a result of a call to {@link #reset()}. * This will always be called from the process's main thread. */ + @MainThread protected void onReset() { } @@ -481,6 +504,7 @@ public class Loader<D> { * * <p>Must be called from the process's main thread. */ + @MainThread public void onContentChanged() { if (mStarted) { forceLoad(); diff --git a/android/support/v4/content/WakefulBroadcastReceiver.java b/android/support/v4/content/WakefulBroadcastReceiver.java index 8ec3eee6..78555aa4 100644 --- a/android/support/v4/content/WakefulBroadcastReceiver.java +++ b/android/support/v4/content/WakefulBroadcastReceiver.java @@ -34,6 +34,9 @@ import android.util.SparseArray; * for you; you must request the {@link android.Manifest.permission#WAKE_LOCK} * permission to use it.</p> * + * <p>Wakelocks held by this class are reported to tools as + * {@code "androidx.core:wake:<component-name>"}.</p> + * * <h3>Example</h3> * * <p>A {@link WakefulBroadcastReceiver} uses the method @@ -103,7 +106,7 @@ public abstract class WakefulBroadcastReceiver extends BroadcastReceiver { PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "wake:" + comp.flattenToShortString()); + "androidx.core:wake:" + comp.flattenToShortString()); wl.setReferenceCounted(false); wl.acquire(60 * 1000); sActiveWakeLocks.put(id, wl); diff --git a/android/support/v4/graphics/TypefaceCompatUtil.java b/android/support/v4/graphics/TypefaceCompatUtil.java index b5d206c8..c524f820 100644 --- a/android/support/v4/graphics/TypefaceCompatUtil.java +++ b/android/support/v4/graphics/TypefaceCompatUtil.java @@ -94,11 +94,15 @@ public class TypefaceCompatUtil { @RequiresApi(19) public static ByteBuffer mmap(Context context, CancellationSignal cancellationSignal, Uri uri) { final ContentResolver resolver = context.getContentResolver(); - try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r", cancellationSignal); - FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) { - FileChannel channel = fis.getChannel(); - final long size = channel.size(); - return channel.map(FileChannel.MapMode.READ_ONLY, 0, size); + try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r", cancellationSignal)) { + if (pfd == null) { + return null; + } + try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) { + FileChannel channel = fis.getChannel(); + final long size = channel.size(); + return channel.map(FileChannel.MapMode.READ_ONLY, 0, size); + } } catch (IOException e) { return null; } diff --git a/android/support/v4/graphics/drawable/DrawableCompat.java b/android/support/v4/graphics/drawable/DrawableCompat.java index 4e988eaf..f15354ef 100644 --- a/android/support/v4/graphics/drawable/DrawableCompat.java +++ b/android/support/v4/graphics/drawable/DrawableCompat.java @@ -229,8 +229,8 @@ public final class DrawableCompat { // children manually if (drawable instanceof InsetDrawable) { clearColorFilter(((InsetDrawable) drawable).getDrawable()); - } else if (drawable instanceof DrawableWrapper) { - clearColorFilter(((DrawableWrapper) drawable).getWrappedDrawable()); + } else if (drawable instanceof WrappedDrawable) { + clearColorFilter(((WrappedDrawable) drawable).getWrappedDrawable()); } else if (drawable instanceof DrawableContainer) { final DrawableContainer container = (DrawableContainer) drawable; final DrawableContainer.DrawableContainerState state = @@ -307,17 +307,17 @@ public final class DrawableCompat { return drawable; } else if (Build.VERSION.SDK_INT >= 21) { if (!(drawable instanceof TintAwareDrawable)) { - return new DrawableWrapperApi21(drawable); + return new WrappedDrawableApi21(drawable); } return drawable; } else if (Build.VERSION.SDK_INT >= 19) { if (!(drawable instanceof TintAwareDrawable)) { - return new DrawableWrapperApi19(drawable); + return new WrappedDrawableApi19(drawable); } return drawable; } else { if (!(drawable instanceof TintAwareDrawable)) { - return new DrawableWrapperApi14(drawable); + return new WrappedDrawableApi14(drawable); } return drawable; } @@ -335,8 +335,8 @@ public final class DrawableCompat { */ @SuppressWarnings("TypeParameterUnusedInFormals") public static <T extends Drawable> T unwrap(@NonNull Drawable drawable) { - if (drawable instanceof DrawableWrapper) { - return (T) ((DrawableWrapper) drawable).getWrappedDrawable(); + if (drawable instanceof WrappedDrawable) { + return (T) ((WrappedDrawable) drawable).getWrappedDrawable(); } return (T) drawable; } diff --git a/android/support/v4/graphics/drawable/DrawableWrapper.java b/android/support/v4/graphics/drawable/WrappedDrawable.java index 1574b363..3bd1d683 100644 --- a/android/support/v4/graphics/drawable/DrawableWrapper.java +++ b/android/support/v4/graphics/drawable/WrappedDrawable.java @@ -28,7 +28,7 @@ import android.support.annotation.RestrictTo; * @hide */ @RestrictTo(LIBRARY_GROUP) -public interface DrawableWrapper { +public interface WrappedDrawable { Drawable getWrappedDrawable(); void setWrappedDrawable(Drawable drawable); } diff --git a/android/support/v4/graphics/drawable/DrawableWrapperApi14.java b/android/support/v4/graphics/drawable/WrappedDrawableApi14.java index 5b1bbc70..d1218bc7 100644 --- a/android/support/v4/graphics/drawable/DrawableWrapperApi14.java +++ b/android/support/v4/graphics/drawable/WrappedDrawableApi14.java @@ -26,7 +26,6 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; /** * Drawable which delegates all calls to its wrapped {@link Drawable}. @@ -34,10 +33,8 @@ import android.support.annotation.RequiresApi; * Also allows backward compatible tinting via a color or {@link ColorStateList}. * This functionality is accessed via static methods in {@code DrawableCompat}. */ - -@RequiresApi(14) -class DrawableWrapperApi14 extends Drawable - implements Drawable.Callback, DrawableWrapper, TintAwareDrawable { +class WrappedDrawableApi14 extends Drawable + implements Drawable.Callback, WrappedDrawable, TintAwareDrawable { static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; @@ -50,7 +47,7 @@ class DrawableWrapperApi14 extends Drawable Drawable mDrawable; - DrawableWrapperApi14(@NonNull DrawableWrapperState state, @Nullable Resources res) { + WrappedDrawableApi14(@NonNull DrawableWrapperState state, @Nullable Resources res) { mState = state; updateLocalState(res); } @@ -60,7 +57,7 @@ class DrawableWrapperApi14 extends Drawable * * @param dr the drawable to wrap */ - DrawableWrapperApi14(@Nullable Drawable dr) { + WrappedDrawableApi14(@Nullable Drawable dr) { mState = mutateConstantState(); // Now set the drawable... setWrappedDrawable(dr); @@ -73,26 +70,17 @@ class DrawableWrapperApi14 extends Drawable */ private void updateLocalState(@Nullable Resources res) { if (mState != null && mState.mDrawableState != null) { - final Drawable dr = newDrawableFromState(mState.mDrawableState, res); - setWrappedDrawable(dr); + setWrappedDrawable(mState.mDrawableState.newDrawable(res)); } } - /** - * Allows us to call ConstantState.newDrawable(*) is a API safe way - */ - protected Drawable newDrawableFromState(@NonNull Drawable.ConstantState state, - @Nullable Resources res) { - return state.newDrawable(res); - } - @Override public void jumpToCurrentState() { mDrawable.jumpToCurrentState(); } @Override - public void draw(Canvas canvas) { + public void draw(@NonNull Canvas canvas) { mDrawable.draw(canvas); } @@ -144,17 +132,19 @@ class DrawableWrapperApi14 extends Drawable } @Override - public boolean setState(final int[] stateSet) { + public boolean setState(@NonNull int[] stateSet) { boolean handled = mDrawable.setState(stateSet); handled = updateTint(stateSet) || handled; return handled; } + @NonNull @Override public int[] getState() { return mDrawable.getState(); } + @NonNull @Override public Drawable getCurrent() { return mDrawable.getCurrent(); @@ -196,7 +186,7 @@ class DrawableWrapperApi14 extends Drawable } @Override - public boolean getPadding(Rect padding) { + public boolean getPadding(@NonNull Rect padding) { return mDrawable.getPadding(padding); } @@ -210,6 +200,7 @@ class DrawableWrapperApi14 extends Drawable return null; } + @NonNull @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { @@ -242,7 +233,7 @@ class DrawableWrapperApi14 extends Drawable * {@inheritDoc} */ @Override - public void invalidateDrawable(Drawable who) { + public void invalidateDrawable(@NonNull Drawable who) { invalidateSelf(); } @@ -250,7 +241,7 @@ class DrawableWrapperApi14 extends Drawable * {@inheritDoc} */ @Override - public void scheduleDrawable(Drawable who, Runnable what, long when) { + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { scheduleSelf(what, when); } @@ -258,7 +249,7 @@ class DrawableWrapperApi14 extends Drawable * {@inheritDoc} */ @Override - public void unscheduleDrawable(Drawable who, Runnable what) { + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { unscheduleSelf(what); } @@ -279,7 +270,7 @@ class DrawableWrapperApi14 extends Drawable } @Override - public void setTintMode(PorterDuff.Mode tintMode) { + public void setTintMode(@NonNull PorterDuff.Mode tintMode) { mState.mTintMode = tintMode; updateTint(getState()); } @@ -364,11 +355,13 @@ class DrawableWrapperApi14 extends Drawable } } + @NonNull @Override public Drawable newDrawable() { return newDrawable(null); } + @NonNull @Override public abstract Drawable newDrawable(@Nullable Resources res); @@ -389,9 +382,10 @@ class DrawableWrapperApi14 extends Drawable super(orig, res); } + @NonNull @Override public Drawable newDrawable(@Nullable Resources res) { - return new DrawableWrapperApi14(this, res); + return new WrappedDrawableApi14(this, res); } } } diff --git a/android/support/v4/graphics/drawable/DrawableWrapperApi19.java b/android/support/v4/graphics/drawable/WrappedDrawableApi19.java index 7707591d..7d6b8d87 100644 --- a/android/support/v4/graphics/drawable/DrawableWrapperApi19.java +++ b/android/support/v4/graphics/drawable/WrappedDrawableApi19.java @@ -23,13 +23,13 @@ import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; @RequiresApi(19) -class DrawableWrapperApi19 extends DrawableWrapperApi14 { +class WrappedDrawableApi19 extends WrappedDrawableApi14 { - DrawableWrapperApi19(Drawable drawable) { + WrappedDrawableApi19(Drawable drawable) { super(drawable); } - DrawableWrapperApi19(DrawableWrapperState state, Resources resources) { + WrappedDrawableApi19(DrawableWrapperState state, Resources resources) { super(state, resources); } @@ -55,9 +55,10 @@ class DrawableWrapperApi19 extends DrawableWrapperApi14 { super(orig, res); } + @NonNull @Override public Drawable newDrawable(@Nullable Resources res) { - return new DrawableWrapperApi19(this, res); + return new WrappedDrawableApi19(this, res); } } } diff --git a/android/support/v4/graphics/drawable/DrawableWrapperApi21.java b/android/support/v4/graphics/drawable/WrappedDrawableApi21.java index 5195cc93..b5507428 100644 --- a/android/support/v4/graphics/drawable/DrawableWrapperApi21.java +++ b/android/support/v4/graphics/drawable/WrappedDrawableApi21.java @@ -16,8 +16,6 @@ package android.support.v4.graphics.drawable; -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Outline; @@ -32,22 +30,21 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; -import android.support.annotation.RestrictTo; import android.util.Log; import java.lang.reflect.Method; @RequiresApi(21) -class DrawableWrapperApi21 extends DrawableWrapperApi19 { - private static final String TAG = "DrawableWrapperApi21"; +class WrappedDrawableApi21 extends WrappedDrawableApi19 { + private static final String TAG = "WrappedDrawableApi21"; private static Method sIsProjectedDrawableMethod; - DrawableWrapperApi21(Drawable drawable) { + WrappedDrawableApi21(Drawable drawable) { super(drawable); findAndCacheIsProjectedDrawableMethod(); } - DrawableWrapperApi21(DrawableWrapperState state, Resources resources) { + WrappedDrawableApi21(DrawableWrapperState state, Resources resources) { super(state, resources); findAndCacheIsProjectedDrawableMethod(); } @@ -63,10 +60,11 @@ class DrawableWrapperApi21 extends DrawableWrapperApi19 { } @Override - public void getOutline(Outline outline) { + public void getOutline(@NonNull Outline outline) { mDrawable.getOutline(outline); } + @NonNull @Override public Rect getDirtyBounds() { return mDrawable.getDirtyBounds(); @@ -100,7 +98,7 @@ class DrawableWrapperApi21 extends DrawableWrapperApi19 { } @Override - public boolean setState(int[] stateSet) { + public boolean setState(@NonNull int[] stateSet) { if (super.setState(stateSet)) { // Manually invalidate because the framework doesn't currently force an invalidation // on a state change @@ -123,9 +121,9 @@ class DrawableWrapperApi21 extends DrawableWrapperApi19 { } /** - * @hide + * This method is overriding hidden framework method in {@link Drawable}. It is used by the + * system and thus it should not be removed. */ - @RestrictTo(LIBRARY_GROUP) public boolean isProjected() { if (mDrawable != null && sIsProjectedDrawableMethod != null) { try { @@ -150,9 +148,10 @@ class DrawableWrapperApi21 extends DrawableWrapperApi19 { super(orig, res); } + @NonNull @Override public Drawable newDrawable(@Nullable Resources res) { - return new DrawableWrapperApi21(this, res); + return new WrappedDrawableApi21(this, res); } } diff --git a/android/support/v4/util/ArraySet.java b/android/support/v4/util/ArraySet.java index ab080fa8..8444d2c0 100644 --- a/android/support/v4/util/ArraySet.java +++ b/android/support/v4/util/ArraySet.java @@ -18,6 +18,8 @@ package android.support.v4.util; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; import android.util.Log; @@ -69,16 +71,15 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * The first entry in the array is a pointer to the next array in the * list; the second entry is a pointer to the int[] hash code array for it. */ - static Object[] sBaseCache; - static int sBaseCacheSize; - static Object[] sTwiceBaseCache; - static int sTwiceBaseCacheSize; + private static Object[] sBaseCache; + private static int sBaseCacheSize; + private static Object[] sTwiceBaseCache; + private static int sTwiceBaseCacheSize; - final boolean mIdentityHashCode; - int[] mHashes; - Object[] mArray; - int mSize; - MapCollections<E, E> mCollections; + private int[] mHashes; + private Object[] mArray; + private int mSize; + private MapCollections<E, E> mCollections; private int indexOf(Object key, int hash) { final int N = mSize; @@ -238,19 +239,13 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * will grow once items are added to it. */ public ArraySet() { - this(0, false); + this(0); } /** * Create a new ArraySet with a given initial capacity. */ public ArraySet(int capacity) { - this(capacity, false); - } - - /** {@hide} */ - public ArraySet(int capacity, boolean identityHashCode) { - mIdentityHashCode = identityHashCode; if (capacity == 0) { mHashes = INT; mArray = OBJECT; @@ -263,15 +258,17 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { /** * Create a new ArraySet with the mappings from the given ArraySet. */ - public ArraySet(ArraySet<E> set) { + public ArraySet(@Nullable ArraySet<E> set) { this(); if (set != null) { addAll(set); } } - /** {@hide} */ - public ArraySet(Collection<E> set) { + /** + * Create a new ArraySet with the mappings from the given {@link Collection}. + */ + public ArraySet(@Nullable Collection<E> set) { this(); if (set != null) { addAll(set); @@ -326,8 +323,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * @return Returns the index of the value if it exists, else a negative integer. */ public int indexOf(Object key) { - return key == null ? indexOfNull() - : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode()); + return key == null ? indexOfNull() : indexOf(key, key.hashCode()); } /** @@ -335,6 +331,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * @param index The desired index, must be between 0 and {@link #size()}-1. * @return Returns the value stored at the given index. */ + @Nullable public E valueAt(int index) { return (E) mArray[index]; } @@ -357,14 +354,14 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * when the class of the object is inappropriate for this set. */ @Override - public boolean add(E value) { + public boolean add(@Nullable E value) { final int hash; int index; if (value == null) { hash = 0; index = indexOfNull(); } else { - hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode(); + hash = value.hashCode(); index = indexOf(value, hash); } if (index >= 0) { @@ -413,8 +410,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { @RestrictTo(LIBRARY_GROUP) public void append(E value) { final int index = mSize; - final int hash = value == null ? 0 - : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode()); + final int hash = value == null ? 0 : value.hashCode(); if (index >= mHashes.length) { throw new IllegalStateException("Array is full"); } @@ -439,7 +435,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * Perform a {@link #add(Object)} of all values in <var>array</var> * @param array The array whose contents are to be retrieved. */ - public void addAll(ArraySet<? extends E> array) { + public void addAll(@NonNull ArraySet<? extends E> array) { final int N = array.mSize; ensureCapacity(mSize + N); if (mSize == 0) { @@ -555,6 +551,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { return mSize; } + @NonNull @Override public Object[] toArray() { Object[] result = new Object[mSize]; @@ -562,8 +559,9 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { return result; } + @NonNull @Override - public <T> T[] toArray(T[] array) { + public <T> T[] toArray(@NonNull T[] array) { if (array.length < mSize) { @SuppressWarnings("unchecked") T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), mSize); @@ -732,7 +730,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * in <var>collection</var>, else returns false. */ @Override - public boolean containsAll(Collection<?> collection) { + public boolean containsAll(@NonNull Collection<?> collection) { Iterator<?> it = collection.iterator(); while (it.hasNext()) { if (!contains(it.next())) { @@ -747,7 +745,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * @param collection The collection whose contents are to be retrieved. */ @Override - public boolean addAll(Collection<? extends E> collection) { + public boolean addAll(@NonNull Collection<? extends E> collection) { ensureCapacity(mSize + collection.size()); boolean added = false; for (E value : collection) { @@ -762,7 +760,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * @return Returns true if any values were removed from the array set, else false. */ @Override - public boolean removeAll(Collection<?> collection) { + public boolean removeAll(@NonNull Collection<?> collection) { boolean removed = false; for (Object value : collection) { removed |= remove(value); @@ -777,7 +775,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { * @return Returns true if any values were removed from the array set, else false. */ @Override - public boolean retainAll(Collection<?> collection) { + public boolean retainAll(@NonNull Collection<?> collection) { boolean removed = false; for (int i = mSize - 1; i >= 0; i--) { if (!collection.contains(mArray[i])) { diff --git a/android/support/v4/util/LongSparseArray.java b/android/support/v4/util/LongSparseArray.java index 25b6bb97..febb5d52 100644 --- a/android/support/v4/util/LongSparseArray.java +++ b/android/support/v4/util/LongSparseArray.java @@ -235,6 +235,14 @@ public class LongSparseArray<E> implements Cloneable { } /** + * Return true if size() is 0. + * @return true if size() is 0. + */ + public boolean isEmpty() { + return size() == 0; + } + + /** * Given an index in the range <code>0...size()-1</code>, returns * the key from the <code>index</code>th key-value mapping that this * LongSparseArray stores. diff --git a/android/support/v4/util/ObjectsCompat.java b/android/support/v4/util/ObjectsCompat.java index b6c740e5..747cfb45 100644 --- a/android/support/v4/util/ObjectsCompat.java +++ b/android/support/v4/util/ObjectsCompat.java @@ -18,6 +18,7 @@ package android.support.v4.util; import android.os.Build; import android.support.annotation.Nullable; +import java.util.Arrays; import java.util.Objects; /** @@ -51,4 +52,46 @@ public class ObjectsCompat { return (a == b) || (a != null && a.equals(b)); } } + + /** + * Returns the hash code of a non-{@code null} argument and 0 for a {@code null} argument. + * + * @param o an object + * @return the hash code of a non-{@code null} argument and 0 for a {@code null} argument + * @see Object#hashCode + */ + public static int hashCode(@Nullable Object o) { + return o != null ? o.hashCode() : 0; + } + + /** + * Generates a hash code for a sequence of input values. The hash code is generated as if all + * the input values were placed into an array, and that array were hashed by calling + * {@link Arrays#hashCode(Object[])}. + * + * <p>This method is useful for implementing {@link Object#hashCode()} on objects containing + * multiple fields. For example, if an object that has three fields, {@code x}, {@code y}, and + * {@code z}, one could write: + * + * <blockquote><pre> + * @Override public int hashCode() { + * return ObjectsCompat.hash(x, y, z); + * } + * </pre></blockquote> + * + * <b>Warning: When a single object reference is supplied, the returned value does not equal the + * hash code of that object reference.</b> This value can be computed by calling + * {@link #hashCode(Object)}. + * + * @param values the values to be hashed + * @return a hash value of the sequence of input values + * @see Arrays#hashCode(Object[]) + */ + public static int hash(@Nullable Object... values) { + if (Build.VERSION.SDK_INT >= 19) { + return Objects.hash(values); + } else { + return Arrays.hashCode(values); + } + } } diff --git a/android/support/v4/util/SparseArrayCompat.java b/android/support/v4/util/SparseArrayCompat.java index aedc4ad0..5238cf06 100644 --- a/android/support/v4/util/SparseArrayCompat.java +++ b/android/support/v4/util/SparseArrayCompat.java @@ -228,6 +228,14 @@ public class SparseArrayCompat<E> implements Cloneable { } /** + * Return true if size() is 0. + * @return true if size() is 0. + */ + public boolean isEmpty() { + return size() == 0; + } + + /** * Given an index in the range <code>0...size()-1</code>, returns * the key from the <code>index</code>th key-value mapping that this * SparseArray stores. diff --git a/android/support/v4/view/ViewCompat.java b/android/support/v4/view/ViewCompat.java index 204a1218..abdaa1ad 100644 --- a/android/support/v4/view/ViewCompat.java +++ b/android/support/v4/view/ViewCompat.java @@ -60,6 +60,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; /** * Helper for accessing features in {@link View}. @@ -443,6 +444,7 @@ public class ViewCompat { private static Field sMinHeightField; private static boolean sMinHeightFieldFetched; private static WeakHashMap<View, String> sTransitionNameMap; + private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); private Method mDispatchStartTemporaryDetach; private Method mDispatchFinishTemporaryDetach; private boolean mTempDetachBound; @@ -1020,6 +1022,21 @@ public class ViewCompat { public boolean isImportantForAutofill(@NonNull View v) { return true; } + + /** + * {@link ViewCompat#generateViewId()} + */ + public int generateViewId() { + for (;;) { + final int result = sNextGeneratedId.get(); + // aapt-generated IDs have the high byte nonzero; clamp to the range under that. + int newValue = result + 1; + if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. + if (sNextGeneratedId.compareAndSet(result, newValue)) { + return result; + } + } + } } @RequiresApi(15) @@ -1178,6 +1195,11 @@ public class ViewCompat { public Display getDisplay(View view) { return view.getDisplay(); } + + @Override + public int generateViewId() { + return View.generateViewId(); + } } @RequiresApi(18) @@ -2413,6 +2435,30 @@ public class ViewCompat { } /** + * Finds the first descendant view with the given ID, the view itself if the ID matches + * {@link View#getId()}, or throws an IllegalArgumentException if the ID is invalid or there + * is no matching view in the hierarchy. + * <p> + * <strong>Note:</strong> In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#findViewById(int) + */ + @NonNull + public static <T extends View> T requireViewById(@NonNull View view, @IdRes int id) { + // TODO: use and link to View#requireViewById() directly, once available + T targetView = view.findViewById(id); + if (targetView == null) { + throw new IllegalArgumentException("ID does not reference a View inside this View"); + } + return targetView; + } + + /** * Indicates whether this View is opaque. An opaque View guarantees that it will * draw all the pixels overlapping its bounds using a fully opaque color. * @@ -3931,5 +3977,15 @@ public class ViewCompat { return IMPL.hasExplicitFocusable(view); } + /** + * Generate a value suitable for use in {@link View#setId(int)}. + * This value will not collide with ID values generated at build time by aapt for R.id. + * + * @return a generated ID value + */ + public static int generateViewId() { + return IMPL.generateViewId(); + } + protected ViewCompat() {} } diff --git a/android/support/v4/view/ViewConfigurationCompat.java b/android/support/v4/view/ViewConfigurationCompat.java index 60d37a9f..a12387b1 100644 --- a/android/support/v4/view/ViewConfigurationCompat.java +++ b/android/support/v4/view/ViewConfigurationCompat.java @@ -117,5 +117,17 @@ public final class ViewConfigurationCompat { return 0; } + /** + * @param config Used to get the hover slop directly from the {@link ViewConfiguration}. + * + * @return The hover slop value. + */ + public static int getScaledHoverSlop(ViewConfiguration config) { + if (android.os.Build.VERSION.SDK_INT >= 28) { + return config.getScaledHoverSlop(); + } + return config.getScaledTouchSlop() / 2; + } + private ViewConfigurationCompat() {} } diff --git a/android/support/v4/view/WindowCompat.java b/android/support/v4/view/WindowCompat.java index cdf4789c..dd0a736c 100644 --- a/android/support/v4/view/WindowCompat.java +++ b/android/support/v4/view/WindowCompat.java @@ -16,6 +16,8 @@ package android.support.v4.view; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; import android.view.View; import android.view.Window; @@ -59,4 +61,29 @@ public final class WindowCompat { public static final int FEATURE_ACTION_MODE_OVERLAY = 10; private WindowCompat() {} + + /** + * Finds a view that was identified by the {@code android:id} XML attribute + * that was processed in {@link android.app.Activity#onCreate}, or throws an + * IllegalArgumentException if the ID is invalid, or there is no matching view in the hierarchy. + * <p> + * <strong>Note:</strong> In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see ViewCompat#requireViewById(View, int) + * @see Window#findViewById(int) + */ + @NonNull + public static <T extends View> T requireViewById(@NonNull Window window, @IdRes int id) { + // TODO: use and link to Window#requireViewById() directly, once available + T view = window.findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Window"); + } + return view; + } } diff --git a/android/support/v4/widget/ContentLoadingProgressBar.java b/android/support/v4/widget/ContentLoadingProgressBar.java index 356c7b9e..631bec5b 100644 --- a/android/support/v4/widget/ContentLoadingProgressBar.java +++ b/android/support/v4/widget/ContentLoadingProgressBar.java @@ -93,9 +93,10 @@ public class ContentLoadingProgressBar extends ProgressBar { * hidden until it has been shown for at least a minimum show time. If the * progress view was not yet visible, cancels showing the progress view. */ - public void hide() { + public synchronized void hide() { mDismissed = true; removeCallbacks(mDelayedShow); + mPostedShow = false; long diff = System.currentTimeMillis() - mStartTime; if (diff >= MIN_SHOW_TIME || mStartTime == -1) { // The progress spinner has been shown long enough @@ -117,11 +118,12 @@ public class ContentLoadingProgressBar extends ProgressBar { * Show the progress view after waiting for a minimum delay. If * during that time, hide() is called, the view is never made visible. */ - public void show() { + public synchronized void show() { // Reset the start time. mStartTime = -1; mDismissed = false; removeCallbacks(mDelayedHide); + mPostedHide = false; if (!mPostedShow) { postDelayed(mDelayedShow, MIN_DELAY); mPostedShow = true; diff --git a/android/support/v4/widget/CursorAdapter.java b/android/support/v4/widget/CursorAdapter.java index e68229e0..3ea6fc8a 100644 --- a/android/support/v4/widget/CursorAdapter.java +++ b/android/support/v4/widget/CursorAdapter.java @@ -43,55 +43,55 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, CursorFilter.CursorFilterClient { /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected boolean mDataValid; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected boolean mAutoRequery; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected Cursor mCursor; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected Context mContext; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected int mRowIDColumn; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected ChangeObserver mChangeObserver; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected DataSetObserver mDataSetObserver; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected CursorFilter mCursorFilter; /** * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected FilterQueryProvider mFilterQueryProvider; diff --git a/android/support/v4/widget/SimpleCursorAdapter.java b/android/support/v4/widget/SimpleCursorAdapter.java index 291f9e15..ba3ee506 100644 --- a/android/support/v4/widget/SimpleCursorAdapter.java +++ b/android/support/v4/widget/SimpleCursorAdapter.java @@ -37,14 +37,14 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { /** * A list of columns containing the data to bind to the UI. * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected int[] mFrom; /** * A list of View ids representing the views to which the data must be bound. * This field should be made private, so it is hidden from the SDK. - * {@hide} + * @hide */ @RestrictTo(LIBRARY_GROUP) protected int[] mTo; diff --git a/android/support/v4/widget/TextViewCompat.java b/android/support/v4/widget/TextViewCompat.java index dc87a38b..8789815f 100644 --- a/android/support/v4/widget/TextViewCompat.java +++ b/android/support/v4/widget/TextViewCompat.java @@ -18,6 +18,11 @@ package android.support.v4.widget; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.DrawableRes; @@ -28,14 +33,22 @@ import android.support.annotation.RequiresApi; import android.support.annotation.RestrictTo; import android.support.annotation.StyleRes; import android.support.v4.os.BuildCompat; +import android.text.Editable; import android.util.Log; import android.util.TypedValue; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.widget.TextView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; /** * Helper for accessing features in {@link TextView}. @@ -219,6 +232,11 @@ public final class TextViewCompat { } return new int[0]; } + + public void setCustomSelectionActionModeCallback(TextView textView, + ActionMode.Callback callback) { + textView.setCustomSelectionActionModeCallback(callback); + } } @RequiresApi(16) @@ -314,8 +332,160 @@ public final class TextViewCompat { } } + @RequiresApi(26) + static class TextViewCompatApi26Impl extends TextViewCompatApi23Impl { + @Override + public void setCustomSelectionActionModeCallback(final TextView textView, + final ActionMode.Callback callback) { + if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O + && Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1) { + super.setCustomSelectionActionModeCallback(textView, callback); + return; + } + + + // A bug in O and O_MR1 causes a number of options for handling the ACTION_PROCESS_TEXT + // intent after selection to not be displayed in the menu, although they should be. + // Here we fix this, by removing the menu items created by the framework code, and + // adding them (and the missing ones) back correctly. + textView.setCustomSelectionActionModeCallback(new ActionMode.Callback() { + // This constant should be correlated with its definition in the + // android.widget.Editor class. + private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100; + + // References to the MenuBuilder class and its removeItemAt(int) method. + // Since in most cases the menu instance processed by this callback is going + // to be a MenuBuilder, we keep these references to avoid querying for them + // frequently by reflection in recomputeProcessTextMenuItems. + private Class mMenuBuilderClass; + private Method mMenuBuilderRemoveItemAtMethod; + private boolean mCanUseMenuBuilderReferences; + private boolean mInitializedMenuBuilderReferences = false; + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return callback.onCreateActionMode(mode, menu); + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + recomputeProcessTextMenuItems(menu); + return callback.onPrepareActionMode(mode, menu); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return callback.onActionItemClicked(mode, item); + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + callback.onDestroyActionMode(mode); + } + + private void recomputeProcessTextMenuItems(final Menu menu) { + final Context context = textView.getContext(); + final PackageManager packageManager = context.getPackageManager(); + + if (!mInitializedMenuBuilderReferences) { + mInitializedMenuBuilderReferences = true; + try { + mMenuBuilderClass = + Class.forName("com.android.internal.view.menu.MenuBuilder"); + mMenuBuilderRemoveItemAtMethod = mMenuBuilderClass + .getDeclaredMethod("removeItemAt", Integer.TYPE); + mCanUseMenuBuilderReferences = true; + } catch (ClassNotFoundException | NoSuchMethodException e) { + mMenuBuilderClass = null; + mMenuBuilderRemoveItemAtMethod = null; + mCanUseMenuBuilderReferences = false; + } + } + // Remove the menu items created for ACTION_PROCESS_TEXT handlers. + try { + final Method removeItemAtMethod = + (mCanUseMenuBuilderReferences && mMenuBuilderClass.isInstance(menu)) + ? mMenuBuilderRemoveItemAtMethod + : menu.getClass() + .getDeclaredMethod("removeItemAt", Integer.TYPE); + for (int i = menu.size() - 1; i >= 0; --i) { + final MenuItem item = menu.getItem(i); + if (item.getIntent() != null && Intent.ACTION_PROCESS_TEXT + .equals(item.getIntent().getAction())) { + removeItemAtMethod.invoke(menu, i); + } + } + } catch (NoSuchMethodException | IllegalAccessException + | InvocationTargetException e) { + // There is a menu custom implementation used which is not providing + // a removeItemAt(int) menu. There is nothing we can do in this case. + return; + } + + // Populate the menu again with the ACTION_PROCESS_TEXT handlers. + final List<ResolveInfo> supportedActivities = + getSupportedActivities(context, packageManager); + for (int i = 0; i < supportedActivities.size(); ++i) { + final ResolveInfo info = supportedActivities.get(i); + menu.add(Menu.NONE, Menu.NONE, + MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i, + info.loadLabel(packageManager)) + .setIntent(createProcessTextIntentForResolveInfo(info, textView)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } + } + + private List<ResolveInfo> getSupportedActivities(final Context context, + final PackageManager packageManager) { + final List<ResolveInfo> supportedActivities = new ArrayList<>(); + boolean canStartActivityForResult = context instanceof Activity; + if (!canStartActivityForResult) { + return supportedActivities; + } + final List<ResolveInfo> unfiltered = + packageManager.queryIntentActivities(createProcessTextIntent(), 0); + for (ResolveInfo info : unfiltered) { + if (isSupportedActivity(info, context)) { + supportedActivities.add(info); + } + } + return supportedActivities; + } + + private boolean isSupportedActivity(final ResolveInfo info, final Context context) { + if (context.getPackageName().equals(info.activityInfo.packageName)) { + return true; + } + if (!info.activityInfo.exported) { + return false; + } + return info.activityInfo.permission == null + || context.checkSelfPermission(info.activityInfo.permission) + == PackageManager.PERMISSION_GRANTED; + } + + private Intent createProcessTextIntentForResolveInfo(final ResolveInfo info, + final TextView textView) { + return createProcessTextIntent() + .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, !isEditable(textView)) + .setClassName(info.activityInfo.packageName, info.activityInfo.name); + } + + private boolean isEditable(final TextView textView) { + return textView instanceof Editable + && textView.onCheckIsTextEditor() + && textView.isEnabled(); + } + + private Intent createProcessTextIntent() { + return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain"); + } + }); + } + } + @RequiresApi(27) - static class TextViewCompatApi27Impl extends TextViewCompatApi23Impl { + static class TextViewCompatApi27Impl extends TextViewCompatApi26Impl { @Override public void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) { textView.setAutoSizeTextTypeWithDefaults(autoSizeTextType); @@ -369,6 +539,8 @@ public final class TextViewCompat { static { if (BuildCompat.isAtLeastOMR1()) { IMPL = new TextViewCompatApi27Impl(); + } else if (Build.VERSION.SDK_INT >= 26) { + IMPL = new TextViewCompatApi26Impl(); } else if (Build.VERSION.SDK_INT >= 23) { IMPL = new TextViewCompatApi23Impl(); } else if (Build.VERSION.SDK_INT >= 18) { @@ -600,4 +772,31 @@ public final class TextViewCompat { public static int[] getAutoSizeTextAvailableSizes(@NonNull TextView textView) { return IMPL.getAutoSizeTextAvailableSizes(textView); } + + /** + * Sets a selection action mode callback on a TextView. + * + * Also this method can be used to fix a bug in framework SDK 26. On these affected devices, + * the bug causes the menu containing the options for handling ACTION_PROCESS_TEXT after text + * selection to miss a number of items. This method can be used to fix this wrong behaviour for + * a text view, by passing any custom callback implementation. If no custom callback is desired, + * a no-op implementation should be provided. + * + * Note that, by default, the bug will only be fixed when the default floating toolbar menu + * implementation is used. If a custom implementation of {@link Menu} is provided, this should + * provide the method Menu#removeItemAt(int) which removes a menu item by its position, + * as given by Menu#getItem(int). Also, the following post condition should hold: a call + * to removeItemAt(i), should not modify the results of getItem(j) for any j < i. Intuitively, + * removing an element from the menu should behave as removing an element from a list. + * Note that this method does not exist in the {@link Menu} interface. However, it is required, + * and going to be called by reflection, in order to display the correct process text items in + * the menu. + * + * @param textView The TextView to set the action selection mode callback on. + * @param callback The action selection mode callback to set on textView. + */ + public static void setCustomSelectionActionModeCallback(@NonNull TextView textView, + @NonNull ActionMode.Callback callback) { + IMPL.setCustomSelectionActionModeCallback(textView, callback); + } } diff --git a/android/support/v7/preference/Preference.java b/android/support/v7/preference/Preference.java index fa8461db..88262cd9 100644 --- a/android/support/v7/preference/Preference.java +++ b/android/support/v7/preference/Preference.java @@ -16,6 +16,7 @@ package android.support.v7.preference; +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.content.Context; @@ -1297,6 +1298,7 @@ public class Preference implements Comparable<Preference> { * preference was removed, modified, and re-added to a {@link PreferenceGroup} * @hide */ + @RestrictTo(LIBRARY) public final boolean wasDetached() { return mWasDetached; } @@ -1305,6 +1307,7 @@ public class Preference implements Comparable<Preference> { * Clears the {@link #wasDetached()} status * @hide */ + @RestrictTo(LIBRARY) public final void clearWasDetached() { mWasDetached = false; } diff --git a/android/support/v7/recyclerview/extensions/ListAdapter.java b/android/support/v7/recyclerview/extensions/ListAdapter.java index 8b28072e..721e0da4 100644 --- a/android/support/v7/recyclerview/extensions/ListAdapter.java +++ b/android/support/v7/recyclerview/extensions/ListAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 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. @@ -17,6 +17,8 @@ package android.support.v7.recyclerview.extensions; import android.support.annotation.NonNull; +import android.support.v7.util.AdapterListUpdateCallback; +import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import java.util.List; @@ -66,7 +68,8 @@ import java.util.List; * public void onBindViewHolder(UserViewHolder holder, int position) { * holder.bindTo(getItem(position)); * } - * public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() { + * public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = + * new DiffUtil.ItemCallback<User>() { * {@literal @}Override * public boolean areItemsTheSame( * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { @@ -95,14 +98,14 @@ public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder> private final ListAdapterHelper<T> mHelper; @SuppressWarnings("unused") - protected ListAdapter(@NonNull DiffCallback<T> diffCallback) { - mHelper = new ListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), - new ListAdapterConfig.Builder<T>().setDiffCallback(diffCallback).build()); + protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) { + mHelper = new ListAdapterHelper<>(new AdapterListUpdateCallback(this), + new ListAdapterConfig.Builder<>(diffCallback).build()); } @SuppressWarnings("unused") protected ListAdapter(@NonNull ListAdapterConfig<T> config) { - mHelper = new ListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), config); + mHelper = new ListAdapterHelper<>(new AdapterListUpdateCallback(this), config); } /** diff --git a/android/support/v7/recyclerview/extensions/ListAdapterConfig.java b/android/support/v7/recyclerview/extensions/ListAdapterConfig.java index 25697a11..53fe4bbf 100644 --- a/android/support/v7/recyclerview/extensions/ListAdapterConfig.java +++ b/android/support/v7/recyclerview/extensions/ListAdapterConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 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. @@ -16,79 +16,91 @@ package android.support.v7.recyclerview.extensions; -import android.arch.core.executor.ArchTaskExecutor; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; +import android.support.v7.util.DiffUtil; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** * Configuration object for {@link ListAdapter}, {@link ListAdapterHelper}, and similar * background-thread list diffing adapter logic. * <p> - * At minimum, defines item diffing behavior with a {@link DiffCallback}, used to compute item - * differences to pass to a RecyclerView adapter. + * At minimum, defines item diffing behavior with a {@link DiffUtil.ItemCallback}, used to compute + * item differences to pass to a RecyclerView adapter. * * @param <T> Type of items in the lists, and being compared. */ public final class ListAdapterConfig<T> { + @NonNull private final Executor mMainThreadExecutor; + @NonNull private final Executor mBackgroundThreadExecutor; - private final DiffCallback<T> mDiffCallback; + @NonNull + private final DiffUtil.ItemCallback<T> mDiffCallback; - private ListAdapterConfig(Executor mainThreadExecutor, Executor backgroundThreadExecutor, - DiffCallback<T> diffCallback) { + private ListAdapterConfig( + @NonNull Executor mainThreadExecutor, + @NonNull Executor backgroundThreadExecutor, + @NonNull DiffUtil.ItemCallback<T> diffCallback) { mMainThreadExecutor = mainThreadExecutor; mBackgroundThreadExecutor = backgroundThreadExecutor; mDiffCallback = diffCallback; } + /** @hide */ + @SuppressWarnings("WeakerAccess") + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @NonNull public Executor getMainThreadExecutor() { return mMainThreadExecutor; } + /** @hide */ + @SuppressWarnings("WeakerAccess") + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @NonNull public Executor getBackgroundThreadExecutor() { return mBackgroundThreadExecutor; } - public DiffCallback<T> getDiffCallback() { + @SuppressWarnings("WeakerAccess") + @NonNull + public DiffUtil.ItemCallback<T> getDiffCallback() { return mDiffCallback; } /** * Builder class for {@link ListAdapterConfig}. - * <p> - * You must at minimum specify a DiffCallback with {@link #setDiffCallback(DiffCallback)} * * @param <T> */ public static class Builder<T> { private Executor mMainThreadExecutor; private Executor mBackgroundThreadExecutor; - private DiffCallback<T> mDiffCallback; + private final DiffUtil.ItemCallback<T> mDiffCallback; - /** - * The {@link DiffCallback} to be used while diffing an old list with the updated one. - * Must be provided. - * - * @param diffCallback The {@link DiffCallback} instance to compare items in the list. - * @return this - */ - @SuppressWarnings("WeakerAccess") - public ListAdapterConfig.Builder<T> setDiffCallback(DiffCallback<T> diffCallback) { + public Builder(@NonNull DiffUtil.ItemCallback<T> diffCallback) { mDiffCallback = diffCallback; - return this; } /** * If provided, defines the main thread executor used to dispatch adapter update * notifications on the main thread. * <p> - * If not provided, it will default to the UI thread. + * If not provided, it will default to the main thread. * * @param executor The executor which can run tasks in the UI thread. * @return this + * + * @hide */ - @SuppressWarnings("unused") - public ListAdapterConfig.Builder<T> setMainThreadExecutor(Executor executor) { + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + @NonNull + public Builder<T> setMainThreadExecutor(Executor executor) { mMainThreadExecutor = executor; return this; } @@ -97,36 +109,55 @@ public final class ListAdapterConfig<T> { * If provided, defines the background executor used to calculate the diff between an old * and a new list. * <p> - * If not provided, defaults to the IO thread pool from Architecture Components. + * If not provided, defaults to two thread pool executor, shared by all ListAdapterConfigs. * * @param executor The background executor to run list diffing. * @return this */ - @SuppressWarnings("unused") - public ListAdapterConfig.Builder<T> setBackgroundThreadExecutor(Executor executor) { + @SuppressWarnings({"unused", "WeakerAccess"}) + @NonNull + public Builder<T> setBackgroundThreadExecutor(Executor executor) { mBackgroundThreadExecutor = executor; return this; } + private static class MainThreadExecutor implements Executor { + final Handler mHandler = new Handler(Looper.getMainLooper()); + @Override + public void execute(@NonNull Runnable command) { + mHandler.post(command); + } + } + /** * Creates a {@link ListAdapterHelper} with the given parameters. * * @return A new ListAdapterConfig. */ + @NonNull public ListAdapterConfig<T> build() { - if (mDiffCallback == null) { - throw new IllegalArgumentException("Must provide a diffCallback"); + if (mMainThreadExecutor == null) { + mMainThreadExecutor = sMainThreadExecutor; } if (mBackgroundThreadExecutor == null) { - mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor(); - } - if (mMainThreadExecutor == null) { - mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor(); + synchronized (sExecutorLock) { + if (sDiffExecutor == null) { + sDiffExecutor = Executors.newFixedThreadPool(2); + } + } + mBackgroundThreadExecutor = sDiffExecutor; } return new ListAdapterConfig<>( mMainThreadExecutor, mBackgroundThreadExecutor, mDiffCallback); } + + // TODO: remove the below once supportlib has its own appropriate executors + private static final Object sExecutorLock = new Object(); + private static Executor sDiffExecutor = null; + + // TODO: use MainThreadExecutor from supportlib once one exists + private static final Executor sMainThreadExecutor = new MainThreadExecutor(); } } diff --git a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java index d0c7bb3e..bb231b17 100644 --- a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java +++ b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 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. @@ -16,8 +16,8 @@ package android.support.v7.recyclerview.extensions; -import android.arch.lifecycle.LiveData; -import android.support.annotation.RestrictTo; +import android.support.annotation.NonNull; +import android.support.v7.util.AdapterListUpdateCallback; import android.support.v7.util.DiffUtil; import android.support.v7.util.ListUpdateCallback; import android.support.v7.widget.RecyclerView; @@ -25,17 +25,18 @@ import android.support.v7.widget.RecyclerView; import java.util.List; /** - * Helper object for displaying a List in {@link RecyclerView.Adapter RecyclerView.Adapter}, which - * signals the adapter of changes when the List is changed by computing changes with DiffUtil in the + * Helper object for displaying a List in + * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, which signals the + * adapter of changes when the List is changed by computing changes with DiffUtil in the * background. * <p> * For simplicity, the {@link ListAdapter} wrapper class can often be used instead of the * helper directly. This helper class is exposed for complex cases, and where overriding an adapter * base class to support List diffing isn't convenient. * <p> - * The ListAdapterHelper can take a {@link LiveData} of List and present the data simply for an - * adapter. It computes differences in List contents via DiffUtil on a background thread as new - * Lists are received. + * The ListAdapterHelper can consume the values from a LiveData of <code>List</code> and present the + * data simply for an adapter. It computes differences in List contents via {@link DiffUtil} on a + * background thread as new <code>List</code>s are received. * <p> * It provides a simple list-like API with {@link #getItem(int)} and {@link #getItemCount()} for an * adapter to acquire and present data objects. @@ -68,10 +69,8 @@ import java.util.List; * } * * class UserAdapter extends RecyclerView.Adapter<UserViewHolder> { - * private final ListAdapterHelper<User> mHelper; - * public UserAdapter(ListAdapterHelper.Builder<User> builder) { - * mHelper = new ListAdapterHelper(this, User.DIFF_CALLBACK); - * } + * private final ListAdapterHelper<User> mHelper = + * new ListAdapterHelper(this, DIFF_CALLBACK); * {@literal @}Override * public int getItemCount() { * return mHelper.getItemCount(); @@ -84,7 +83,8 @@ import java.util.List; * User user = mHelper.getItem(position); * holder.bindTo(user); * } - * public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() { + * public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK + * = new DiffUtil.ItemCallback<User>() { * {@literal @}Override * public boolean areItemsTheSame( * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { @@ -107,47 +107,37 @@ public class ListAdapterHelper<T> { private final ListUpdateCallback mUpdateCallback; private final ListAdapterConfig<T> mConfig; - @SuppressWarnings("WeakerAccess") - public ListAdapterHelper(ListUpdateCallback listUpdateCallback, - ListAdapterConfig<T> config) { - mUpdateCallback = listUpdateCallback; - mConfig = config; + /** + * Convenience for + * {@code PagedListAdapterHelper(new AdapterListUpdateCallback(adapter), + * new ListAdapterConfig.Builder().setDiffCallback(diffCallback).build());} + * + * @param adapter Adapter to dispatch position updates to. + * @param diffCallback ItemCallback that compares items to dispatch appropriate animations when + * + * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter) + */ + public ListAdapterHelper(@NonNull RecyclerView.Adapter adapter, + @NonNull DiffUtil.ItemCallback<T> diffCallback) { + mUpdateCallback = new AdapterListUpdateCallback(adapter); + mConfig = new ListAdapterConfig.Builder<>(diffCallback).build(); } /** - * Default ListUpdateCallback that dispatches directly to an adapter. Can be replaced by a - * custom ListUpdateCallback if e.g. your adapter has a header in it, and so has an offset - * between list positions and adapter positions. + * Create a ListAdapterHelper with the provided config, and ListUpdateCallback to dispatch + * updates to. + * + * @param listUpdateCallback Callback to dispatch updates to. + * @param config Config to define background work Executor, and DiffUtil.ItemCallback for + * computing List diffs. * - * @hide + * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter) */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static class AdapterCallback implements ListUpdateCallback { - private final RecyclerView.Adapter mAdapter; - - public AdapterCallback(RecyclerView.Adapter adapter) { - mAdapter = adapter; - } - - @Override - public void onInserted(int position, int count) { - mAdapter.notifyItemRangeInserted(position, count); - } - - @Override - public void onRemoved(int position, int count) { - mAdapter.notifyItemRangeRemoved(position, count); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - mAdapter.notifyItemMoved(fromPosition, toPosition); - } - - @Override - public void onChanged(int position, int count, Object payload) { - mAdapter.notifyItemRangeChanged(position, count, payload); - } + @SuppressWarnings("WeakerAccess") + public ListAdapterHelper(@NonNull ListUpdateCallback listUpdateCallback, + @NonNull ListAdapterConfig<T> config) { + mUpdateCallback = listUpdateCallback; + mConfig = config; } private List<T> mList; @@ -173,7 +163,8 @@ public class ListAdapterHelper<T> { /** * Get the number of items currently presented by this AdapterHelper. This value can be directly - * returned to {@link RecyclerView.Adapter#getItemCount()}. + * returned to {@link android.support.v7.widget.RecyclerView.Adapter#getItemCount() + * RecyclerView.Adapter.getItemCount()}. * * @return Number of items being presented. */ diff --git a/android/support/v7/util/AdapterListUpdateCallback.java b/android/support/v7/util/AdapterListUpdateCallback.java new file mode 100644 index 00000000..f86ba7d0 --- /dev/null +++ b/android/support/v7/util/AdapterListUpdateCallback.java @@ -0,0 +1,63 @@ +/* + * Copyright 2018 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.v7.util; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; + +/** + * ListUpdateCallback that dispatches update events to the given adapter. + * + * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter) + */ +public final class AdapterListUpdateCallback implements ListUpdateCallback { + @NonNull + private final RecyclerView.Adapter mAdapter; + + /** + * Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter. + * + * @param adapter The Adapter to send updates to. + */ + public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) { + mAdapter = adapter; + } + + /** {@inheritDoc} */ + @Override + public void onInserted(int position, int count) { + mAdapter.notifyItemRangeInserted(position, count); + } + + /** {@inheritDoc} */ + @Override + public void onRemoved(int position, int count) { + mAdapter.notifyItemRangeRemoved(position, count); + } + + /** {@inheritDoc} */ + @Override + public void onMoved(int fromPosition, int toPosition) { + mAdapter.notifyItemMoved(fromPosition, toPosition); + } + + /** {@inheritDoc} */ + @Override + public void onChanged(int position, int count, Object payload) { + mAdapter.notifyItemRangeChanged(position, count, payload); + } +} diff --git a/android/support/v7/util/DiffUtil.java b/android/support/v7/util/DiffUtil.java index ebc33f31..a55a21d5 100644 --- a/android/support/v7/util/DiffUtil.java +++ b/android/support/v7/util/DiffUtil.java @@ -16,7 +16,6 @@ package android.support.v7.util; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.RecyclerView; @@ -369,7 +368,7 @@ public class DiffUtil { * * @see Callback#areItemsTheSame(int, int) */ - public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem); + public abstract boolean areItemsTheSame(T oldItem, T newItem); /** * Called to check whether two items have the same data. @@ -392,7 +391,7 @@ public class DiffUtil { * * @see Callback#areContentsTheSame(int, int) */ - public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem); + public abstract boolean areContentsTheSame(T oldItem, T newItem); /** * When {@link #areItemsTheSame(T, T)} returns {@code true} for two items and @@ -409,7 +408,7 @@ public class DiffUtil { * @see Callback#getChangePayload(int, int) */ @SuppressWarnings({"WeakerAccess", "unused"}) - public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) { + public Object getChangePayload(T oldItem, T newItem) { return null; } } @@ -721,35 +720,16 @@ public class DiffUtil { * * @param adapter A RecyclerView adapter which was displaying the old list and will start * displaying the new list. + * @see AdapterListUpdateCallback */ public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) { - dispatchUpdatesTo(new ListUpdateCallback() { - @Override - public void onInserted(int position, int count) { - adapter.notifyItemRangeInserted(position, count); - } - - @Override - public void onRemoved(int position, int count) { - adapter.notifyItemRangeRemoved(position, count); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - adapter.notifyItemMoved(fromPosition, toPosition); - } - - @Override - public void onChanged(int position, int count, Object payload) { - adapter.notifyItemRangeChanged(position, count, payload); - } - }); + dispatchUpdatesTo(new AdapterListUpdateCallback(adapter)); } /** * Dispatches update operations to the given Callback. * <p> - * These updates are atomic such that the first update call effects every update call that + * These updates are atomic such that the first update call affects every update call that * comes after it (the same as RecyclerView). * * @param updateCallback The callback to receive the update operations. diff --git a/android/support/v7/widget/AppCompatEditText.java b/android/support/v7/widget/AppCompatEditText.java index 6831fcbf..fdda68eb 100644 --- a/android/support/v7/widget/AppCompatEditText.java +++ b/android/support/v7/widget/AppCompatEditText.java @@ -25,8 +25,10 @@ import android.graphics.drawable.Drawable; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; +import android.support.v4.os.BuildCompat; import android.support.v4.view.TintableBackgroundView; import android.support.v7.appcompat.R; +import android.text.Editable; import android.util.AttributeSet; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -71,6 +73,20 @@ public class AppCompatEditText extends EditText implements TintableBackgroundVie mTextHelper.applyCompoundDrawablesTints(); } + /** + * Return the text that the view is displaying. If an editable text has not been set yet, this + * will return null. + */ + @Override + @Nullable public Editable getText() { + if (BuildCompat.isAtLeastP()) { + return super.getText(); + } + // A bug pre-P makes getText() crash if called before the first setText due to a cast, so + // retrieve the editable text. + return super.getEditableText(); + } + @Override public void setBackgroundResource(@DrawableRes int resId) { super.setBackgroundResource(resId); diff --git a/android/support/v7/widget/AppCompatProgressBarHelper.java b/android/support/v7/widget/AppCompatProgressBarHelper.java index 443281e2..a95873cb 100644 --- a/android/support/v7/widget/AppCompatProgressBarHelper.java +++ b/android/support/v7/widget/AppCompatProgressBarHelper.java @@ -27,7 +27,7 @@ import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.Shape; -import android.support.v4.graphics.drawable.DrawableWrapper; +import android.support.v4.graphics.drawable.WrappedDrawable; import android.util.AttributeSet; import android.view.Gravity; import android.widget.ProgressBar; @@ -69,11 +69,11 @@ class AppCompatProgressBarHelper { * traverse layer and state list drawables. */ private Drawable tileify(Drawable drawable, boolean clip) { - if (drawable instanceof DrawableWrapper) { - Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable(); + if (drawable instanceof WrappedDrawable) { + Drawable inner = ((WrappedDrawable) drawable).getWrappedDrawable(); if (inner != null) { inner = tileify(inner, clip); - ((DrawableWrapper) drawable).setWrappedDrawable(inner); + ((WrappedDrawable) drawable).setWrappedDrawable(inner); } } else if (drawable instanceof LayerDrawable) { LayerDrawable background = (LayerDrawable) drawable; diff --git a/android/support/v7/widget/ContentFrameLayout.java b/android/support/v7/widget/ContentFrameLayout.java index 11002805..f777901c 100644 --- a/android/support/v7/widget/ContentFrameLayout.java +++ b/android/support/v7/widget/ContentFrameLayout.java @@ -16,6 +16,7 @@ package android.support.v7.widget; +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; @@ -33,6 +34,7 @@ import android.widget.FrameLayout; /** * @hide */ +@RestrictTo(LIBRARY) public class ContentFrameLayout extends FrameLayout { public interface OnAttachListener { diff --git a/android/support/v7/widget/DrawableUtils.java b/android/support/v7/widget/DrawableUtils.java index c7820b6b..9216726e 100644 --- a/android/support/v7/widget/DrawableUtils.java +++ b/android/support/v7/widget/DrawableUtils.java @@ -30,6 +30,7 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v4.graphics.drawable.WrappedDrawable; import android.util.Log; import java.lang.reflect.Field; @@ -146,9 +147,9 @@ public class DrawableUtils { } } } - } else if (drawable instanceof android.support.v4.graphics.drawable.DrawableWrapper) { + } else if (drawable instanceof WrappedDrawable) { return canSafelyMutateDrawable( - ((android.support.v4.graphics.drawable.DrawableWrapper) drawable) + ((WrappedDrawable) drawable) .getWrappedDrawable()); } else if (drawable instanceof android.support.v7.graphics.drawable.DrawableWrapper) { return canSafelyMutateDrawable( diff --git a/android/support/v7/widget/DropDownListView.java b/android/support/v7/widget/DropDownListView.java index 5cad340e..cccb82be 100644 --- a/android/support/v7/widget/DropDownListView.java +++ b/android/support/v7/widget/DropDownListView.java @@ -17,12 +17,23 @@ package android.support.v7.widget; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Build; +import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.widget.ListViewAutoScrollHelper; import android.support.v7.appcompat.R; +import android.support.v7.graphics.drawable.DrawableWrapper; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.ListAdapter; +import android.widget.ListView; + +import java.lang.reflect.Field; /** * <p>Wrapper class for a ListView. This wrapper can hijack the focus to @@ -30,7 +41,21 @@ import android.view.View; * displayed on screen within a drop down. The focus is never actually * passed to the drop down in this mode; the list only looks focused.</p> */ -class DropDownListView extends ListViewCompat { +class DropDownListView extends ListView { + public static final int INVALID_POSITION = -1; + public static final int NO_POSITION = -1; + + private final Rect mSelectorRect = new Rect(); + private int mSelectionLeftPadding = 0; + private int mSelectionTopPadding = 0; + private int mSelectionRightPadding = 0; + private int mSelectionBottomPadding = 0; + + private int mMotionPosition; + + private Field mIsChildViewEnabled; + + private GateKeeperDrawable mSelector; /* * WARNING: This is a workaround for a touch mode issue. @@ -81,10 +106,306 @@ class DropDownListView extends ListViewCompat { * * @param context this view's context */ - public DropDownListView(Context context, boolean hijackFocus) { + DropDownListView(Context context, boolean hijackFocus) { super(context, null, R.attr.dropDownListViewStyle); mHijackFocus = hijackFocus; setCacheColorHint(0); // Transparent, since the background drawable could be anything. + + try { + mIsChildViewEnabled = AbsListView.class.getDeclaredField("mIsChildViewEnabled"); + mIsChildViewEnabled.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + + + @Override + public boolean isInTouchMode() { + // WARNING: Please read the comment where mListSelectionHidden is declared + return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode(); + } + + /** + * <p>Returns the focus state in the drop down.</p> + * + * @return true always if hijacking focus + */ + @Override + public boolean hasWindowFocus() { + return mHijackFocus || super.hasWindowFocus(); + } + + /** + * <p>Returns the focus state in the drop down.</p> + * + * @return true always if hijacking focus + */ + @Override + public boolean isFocused() { + return mHijackFocus || super.isFocused(); + } + + /** + * <p>Returns the focus state in the drop down.</p> + * + * @return true always if hijacking focus + */ + @Override + public boolean hasFocus() { + return mHijackFocus || super.hasFocus(); + } + + @Override + public void setSelector(Drawable sel) { + mSelector = sel != null ? new GateKeeperDrawable(sel) : null; + super.setSelector(mSelector); + + final Rect padding = new Rect(); + if (sel != null) { + sel.getPadding(padding); + } + + mSelectionLeftPadding = padding.left; + mSelectionTopPadding = padding.top; + mSelectionRightPadding = padding.right; + mSelectionBottomPadding = padding.bottom; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + setSelectorEnabled(true); + updateSelectorStateCompat(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + final boolean drawSelectorOnTop = false; + if (!drawSelectorOnTop) { + drawSelectorCompat(canvas); + } + + super.dispatchDraw(canvas); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mMotionPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); + break; + } + return super.onTouchEvent(ev); + } + + /** + * Find a position that can be selected (i.e., is not a separator). + * + * @param position The starting position to look at. + * @param lookDown Whether to look down for other positions. + * @return The next selectable position starting at position and then searching either up or + * down. Returns {@link #INVALID_POSITION} if nothing can be found. + */ + public int lookForSelectablePosition(int position, boolean lookDown) { + final ListAdapter adapter = getAdapter(); + if (adapter == null || isInTouchMode()) { + return INVALID_POSITION; + } + + final int count = adapter.getCount(); + if (!getAdapter().areAllItemsEnabled()) { + if (lookDown) { + position = Math.max(0, position); + while (position < count && !adapter.isEnabled(position)) { + position++; + } + } else { + position = Math.min(position, count - 1); + while (position >= 0 && !adapter.isEnabled(position)) { + position--; + } + } + + if (position < 0 || position >= count) { + return INVALID_POSITION; + } + return position; + } else { + if (position < 0 || position >= count) { + return INVALID_POSITION; + } + return position; + } + } + + /** + * Measures the height of the given range of children (inclusive) and returns the height + * with this ListView's padding and divider heights included. If maxHeight is provided, the + * measuring will stop when the current height reaches maxHeight. + * + * @param widthMeasureSpec The width measure spec to be given to a child's + * {@link View#measure(int, int)}. + * @param startPosition The position of the first child to be shown. + * @param endPosition The (inclusive) position of the last child to be + * shown. Specify {@link #NO_POSITION} if the last child + * should be the last available child from the adapter. + * @param maxHeight The maximum height that will be returned (if all the + * children don't fit in this value, this value will be + * returned). + * @param disallowPartialChildPosition In general, whether the returned height should only + * contain entire children. This is more powerful--it is + * the first inclusive position at which partial + * children will not be allowed. Example: it looks nice + * to have at least 3 completely visible children, and + * in portrait this will most likely fit; but in + * landscape there could be times when even 2 children + * can not be completely shown, so a value of 2 + * (remember, inclusive) would be good (assuming + * startPosition is 0). + * @return The height of this ListView with the given children. + */ + public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition, + int endPosition, final int maxHeight, + int disallowPartialChildPosition) { + + final int paddingTop = getListPaddingTop(); + final int paddingBottom = getListPaddingBottom(); + final int paddingLeft = getListPaddingLeft(); + final int paddingRight = getListPaddingRight(); + final int reportedDividerHeight = getDividerHeight(); + final Drawable divider = getDivider(); + + final ListAdapter adapter = getAdapter(); + + if (adapter == null) { + return paddingTop + paddingBottom; + } + + // Include the padding of the list + int returnedHeight = paddingTop + paddingBottom; + final int dividerHeight = ((reportedDividerHeight > 0) && divider != null) + ? reportedDividerHeight : 0; + + // The previous height value that was less than maxHeight and contained + // no partial children + int prevHeightWithoutPartialChild = 0; + + View child = null; + int viewType = 0; + int count = adapter.getCount(); + for (int i = 0; i < count; i++) { + int newType = adapter.getItemViewType(i); + if (newType != viewType) { + child = null; + viewType = newType; + } + child = adapter.getView(i, child, this); + + // Compute child height spec + int heightMeasureSpec; + ViewGroup.LayoutParams childLp = child.getLayoutParams(); + + if (childLp == null) { + childLp = generateDefaultLayoutParams(); + child.setLayoutParams(childLp); + } + + if (childLp.height > 0) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height, + MeasureSpec.EXACTLY); + } else { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + child.measure(widthMeasureSpec, heightMeasureSpec); + + // Since this view was measured directly against the parent measure + // spec, we must measure it again before reuse. + child.forceLayout(); + + if (i > 0) { + // Count the divider for all but one child + returnedHeight += dividerHeight; + } + + returnedHeight += child.getMeasuredHeight(); + + if (returnedHeight >= maxHeight) { + // We went over, figure out which height to return. If returnedHeight > + // maxHeight, then the i'th position did not fit completely. + return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) + && (i > disallowPartialChildPosition) // We've past the min pos + && (prevHeightWithoutPartialChild > 0) // We have a prev height + && (returnedHeight != maxHeight) // i'th child did not fit completely + ? prevHeightWithoutPartialChild + : maxHeight; + } + + if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { + prevHeightWithoutPartialChild = returnedHeight; + } + } + + // At this point, we went through the range of children, and they each + // completely fit, so return the returnedHeight + return returnedHeight; + } + + private void setSelectorEnabled(boolean enabled) { + if (mSelector != null) { + mSelector.setEnabled(enabled); + } + } + + private static class GateKeeperDrawable extends DrawableWrapper { + private boolean mEnabled; + + GateKeeperDrawable(Drawable drawable) { + super(drawable); + mEnabled = true; + } + + void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + @Override + public boolean setState(int[] stateSet) { + if (mEnabled) { + return super.setState(stateSet); + } + return false; + } + + @Override + public void draw(Canvas canvas) { + if (mEnabled) { + super.draw(canvas); + } + } + + @Override + public void setHotspot(float x, float y) { + if (mEnabled) { + super.setHotspot(x, y); + } + } + + @Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + if (mEnabled) { + super.setHotspotBounds(left, top, right, bottom); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + if (mEnabled) { + return super.setVisible(visible, restart); + } + return false; + } } /** @@ -169,6 +490,77 @@ class DropDownListView extends ListViewCompat { mListSelectionHidden = hideListSelection; } + private void updateSelectorStateCompat() { + Drawable selector = getSelector(); + if (selector != null && touchModeDrawsInPressedStateCompat() && isPressed()) { + selector.setState(getDrawableState()); + } + } + + private void drawSelectorCompat(Canvas canvas) { + if (!mSelectorRect.isEmpty()) { + final Drawable selector = getSelector(); + if (selector != null) { + selector.setBounds(mSelectorRect); + selector.draw(canvas); + } + } + } + + private void positionSelectorLikeTouchCompat(int position, View sel, float x, float y) { + positionSelectorLikeFocusCompat(position, sel); + + Drawable selector = getSelector(); + if (selector != null && position != INVALID_POSITION) { + DrawableCompat.setHotspot(selector, x, y); + } + } + + private void positionSelectorLikeFocusCompat(int position, View sel) { + // If we're changing position, update the visibility since the selector + // is technically being detached from the previous selection. + final Drawable selector = getSelector(); + final boolean manageState = selector != null && position != INVALID_POSITION; + if (manageState) { + selector.setVisible(false, false); + } + + positionSelectorCompat(position, sel); + + if (manageState) { + final Rect bounds = mSelectorRect; + final float x = bounds.exactCenterX(); + final float y = bounds.exactCenterY(); + selector.setVisible(getVisibility() == VISIBLE, false); + DrawableCompat.setHotspot(selector, x, y); + } + } + + private void positionSelectorCompat(int position, View sel) { + final Rect selectorRect = mSelectorRect; + selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); + + // Adjust for selection padding. + selectorRect.left -= mSelectionLeftPadding; + selectorRect.top -= mSelectionTopPadding; + selectorRect.right += mSelectionRightPadding; + selectorRect.bottom += mSelectionBottomPadding; + + try { + // AbsListView.mIsChildViewEnabled controls the selector's state so we need to + // modify its value + final boolean isChildViewEnabled = mIsChildViewEnabled.getBoolean(this); + if (sel.isEnabled() != isChildViewEnabled) { + mIsChildViewEnabled.set(this, !isChildViewEnabled); + if (position != INVALID_POSITION) { + refreshDrawableState(); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + private void clearPressedItem() { mDrawsInPressedState = false; setPressed(false); @@ -233,44 +625,7 @@ class DropDownListView extends ListViewCompat { refreshDrawableState(); } - @Override - protected boolean touchModeDrawsInPressedStateCompat() { - return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat(); - } - - @Override - public boolean isInTouchMode() { - // WARNING: Please read the comment where mListSelectionHidden is declared - return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode(); - } - - /** - * <p>Returns the focus state in the drop down.</p> - * - * @return true always if hijacking focus - */ - @Override - public boolean hasWindowFocus() { - return mHijackFocus || super.hasWindowFocus(); - } - - /** - * <p>Returns the focus state in the drop down.</p> - * - * @return true always if hijacking focus - */ - @Override - public boolean isFocused() { - return mHijackFocus || super.isFocused(); - } - - /** - * <p>Returns the focus state in the drop down.</p> - * - * @return true always if hijacking focus - */ - @Override - public boolean hasFocus() { - return mHijackFocus || super.hasFocus(); + private boolean touchModeDrawsInPressedStateCompat() { + return mDrawsInPressedState; } } diff --git a/android/support/v7/widget/LinearLayoutCompat.java b/android/support/v7/widget/LinearLayoutCompat.java index f071ae4f..ef68896f 100644 --- a/android/support/v7/widget/LinearLayoutCompat.java +++ b/android/support/v7/widget/LinearLayoutCompat.java @@ -16,6 +16,7 @@ package android.support.v7.widget; +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.content.Context; @@ -559,6 +560,7 @@ public class LinearLayoutCompat extends ViewGroup { * @return true if there should be a divider before the child at childIndex * @hide Pending API consideration. Currently only used internally by the system. */ + @RestrictTo(LIBRARY) protected boolean hasDividerBeforeChildAt(int childIndex) { if (childIndex == 0) { return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0; diff --git a/android/support/v7/widget/ListViewCompat.java b/android/support/v7/widget/ListViewCompat.java deleted file mode 100644 index 3a2fba3b..00000000 --- a/android/support/v7/widget/ListViewCompat.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright (C) 2014 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.v7.widget; - -import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.support.annotation.RestrictTo; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v7.graphics.drawable.DrawableWrapper; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.ListAdapter; -import android.widget.ListView; - -import java.lang.reflect.Field; - -/** - * This class contains a number of useful things for ListView. Mainly used by - * {@link android.support.v7.widget.ListPopupWindow}. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class ListViewCompat extends ListView { - - public static final int INVALID_POSITION = -1; - public static final int NO_POSITION = -1; - - private static final int[] STATE_SET_NOTHING = new int[] { 0 }; - - final Rect mSelectorRect = new Rect(); - int mSelectionLeftPadding = 0; - int mSelectionTopPadding = 0; - int mSelectionRightPadding = 0; - int mSelectionBottomPadding = 0; - - protected int mMotionPosition; - - private Field mIsChildViewEnabled; - - private GateKeeperDrawable mSelector; - - public ListViewCompat(Context context) { - this(context, null); - } - - public ListViewCompat(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ListViewCompat(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - try { - mIsChildViewEnabled = AbsListView.class.getDeclaredField("mIsChildViewEnabled"); - mIsChildViewEnabled.setAccessible(true); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } - } - - @Override - public void setSelector(Drawable sel) { - mSelector = sel != null ? new GateKeeperDrawable(sel) : null; - super.setSelector(mSelector); - - final Rect padding = new Rect(); - if (sel != null) { - sel.getPadding(padding); - } - - mSelectionLeftPadding = padding.left; - mSelectionTopPadding = padding.top; - mSelectionRightPadding = padding.right; - mSelectionBottomPadding = padding.bottom; - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - setSelectorEnabled(true); - updateSelectorStateCompat(); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - final boolean drawSelectorOnTop = false; - if (!drawSelectorOnTop) { - drawSelectorCompat(canvas); - } - - super.dispatchDraw(canvas); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mMotionPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); - break; - } - return super.onTouchEvent(ev); - } - - protected void updateSelectorStateCompat() { - Drawable selector = getSelector(); - if (selector != null && shouldShowSelectorCompat()) { - selector.setState(getDrawableState()); - } - } - - protected boolean shouldShowSelectorCompat() { - return touchModeDrawsInPressedStateCompat() && isPressed(); - } - - protected boolean touchModeDrawsInPressedStateCompat() { - return false; - } - - protected void drawSelectorCompat(Canvas canvas) { - if (!mSelectorRect.isEmpty()) { - final Drawable selector = getSelector(); - if (selector != null) { - selector.setBounds(mSelectorRect); - selector.draw(canvas); - } - } - } - - /** - * Find a position that can be selected (i.e., is not a separator). - * - * @param position The starting position to look at. - * @param lookDown Whether to look down for other positions. - * @return The next selectable position starting at position and then searching either up or - * down. Returns {@link #INVALID_POSITION} if nothing can be found. - */ - public int lookForSelectablePosition(int position, boolean lookDown) { - final ListAdapter adapter = getAdapter(); - if (adapter == null || isInTouchMode()) { - return INVALID_POSITION; - } - - final int count = adapter.getCount(); - if (!getAdapter().areAllItemsEnabled()) { - if (lookDown) { - position = Math.max(0, position); - while (position < count && !adapter.isEnabled(position)) { - position++; - } - } else { - position = Math.min(position, count - 1); - while (position >= 0 && !adapter.isEnabled(position)) { - position--; - } - } - - if (position < 0 || position >= count) { - return INVALID_POSITION; - } - return position; - } else { - if (position < 0 || position >= count) { - return INVALID_POSITION; - } - return position; - } - } - - protected void positionSelectorLikeTouchCompat(int position, View sel, float x, float y) { - positionSelectorLikeFocusCompat(position, sel); - - Drawable selector = getSelector(); - if (selector != null && position != INVALID_POSITION) { - DrawableCompat.setHotspot(selector, x, y); - } - } - - protected void positionSelectorLikeFocusCompat(int position, View sel) { - // If we're changing position, update the visibility since the selector - // is technically being detached from the previous selection. - final Drawable selector = getSelector(); - final boolean manageState = selector != null && position != INVALID_POSITION; - if (manageState) { - selector.setVisible(false, false); - } - - positionSelectorCompat(position, sel); - - if (manageState) { - final Rect bounds = mSelectorRect; - final float x = bounds.exactCenterX(); - final float y = bounds.exactCenterY(); - selector.setVisible(getVisibility() == VISIBLE, false); - DrawableCompat.setHotspot(selector, x, y); - } - } - - protected void positionSelectorCompat(int position, View sel) { - final Rect selectorRect = mSelectorRect; - selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); - - // Adjust for selection padding. - selectorRect.left -= mSelectionLeftPadding; - selectorRect.top -= mSelectionTopPadding; - selectorRect.right += mSelectionRightPadding; - selectorRect.bottom += mSelectionBottomPadding; - - try { - // AbsListView.mIsChildViewEnabled controls the selector's state so we need to - // modify its value - final boolean isChildViewEnabled = mIsChildViewEnabled.getBoolean(this); - if (sel.isEnabled() != isChildViewEnabled) { - mIsChildViewEnabled.set(this, !isChildViewEnabled); - if (position != INVALID_POSITION) { - refreshDrawableState(); - } - } - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - /** - * Measures the height of the given range of children (inclusive) and returns the height - * with this ListView's padding and divider heights included. If maxHeight is provided, the - * measuring will stop when the current height reaches maxHeight. - * - * @param widthMeasureSpec The width measure spec to be given to a child's - * {@link View#measure(int, int)}. - * @param startPosition The position of the first child to be shown. - * @param endPosition The (inclusive) position of the last child to be - * shown. Specify {@link #NO_POSITION} if the last child - * should be the last available child from the adapter. - * @param maxHeight The maximum height that will be returned (if all the - * children don't fit in this value, this value will be - * returned). - * @param disallowPartialChildPosition In general, whether the returned height should only - * contain entire children. This is more powerful--it is - * the first inclusive position at which partial - * children will not be allowed. Example: it looks nice - * to have at least 3 completely visible children, and - * in portrait this will most likely fit; but in - * landscape there could be times when even 2 children - * can not be completely shown, so a value of 2 - * (remember, inclusive) would be good (assuming - * startPosition is 0). - * @return The height of this ListView with the given children. - */ - public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition, - int endPosition, final int maxHeight, - int disallowPartialChildPosition) { - - final int paddingTop = getListPaddingTop(); - final int paddingBottom = getListPaddingBottom(); - final int paddingLeft = getListPaddingLeft(); - final int paddingRight = getListPaddingRight(); - final int reportedDividerHeight = getDividerHeight(); - final Drawable divider = getDivider(); - - final ListAdapter adapter = getAdapter(); - - if (adapter == null) { - return paddingTop + paddingBottom; - } - - // Include the padding of the list - int returnedHeight = paddingTop + paddingBottom; - final int dividerHeight = ((reportedDividerHeight > 0) && divider != null) - ? reportedDividerHeight : 0; - - // The previous height value that was less than maxHeight and contained - // no partial children - int prevHeightWithoutPartialChild = 0; - - View child = null; - int viewType = 0; - int count = adapter.getCount(); - for (int i = 0; i < count; i++) { - int newType = adapter.getItemViewType(i); - if (newType != viewType) { - child = null; - viewType = newType; - } - child = adapter.getView(i, child, this); - - // Compute child height spec - int heightMeasureSpec; - ViewGroup.LayoutParams childLp = child.getLayoutParams(); - - if (childLp == null) { - childLp = generateDefaultLayoutParams(); - child.setLayoutParams(childLp); - } - - if (childLp.height > 0) { - heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height, - MeasureSpec.EXACTLY); - } else { - heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - } - child.measure(widthMeasureSpec, heightMeasureSpec); - - // Since this view was measured directly against the parent measure - // spec, we must measure it again before reuse. - child.forceLayout(); - - if (i > 0) { - // Count the divider for all but one child - returnedHeight += dividerHeight; - } - - returnedHeight += child.getMeasuredHeight(); - - if (returnedHeight >= maxHeight) { - // We went over, figure out which height to return. If returnedHeight > - // maxHeight, then the i'th position did not fit completely. - return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) - && (i > disallowPartialChildPosition) // We've past the min pos - && (prevHeightWithoutPartialChild > 0) // We have a prev height - && (returnedHeight != maxHeight) // i'th child did not fit completely - ? prevHeightWithoutPartialChild - : maxHeight; - } - - if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { - prevHeightWithoutPartialChild = returnedHeight; - } - } - - // At this point, we went through the range of children, and they each - // completely fit, so return the returnedHeight - return returnedHeight; - } - - protected void setSelectorEnabled(boolean enabled) { - if (mSelector != null) { - mSelector.setEnabled(enabled); - } - } - - private static class GateKeeperDrawable extends DrawableWrapper { - private boolean mEnabled; - - public GateKeeperDrawable(Drawable drawable) { - super(drawable); - mEnabled = true; - } - - void setEnabled(boolean enabled) { - mEnabled = enabled; - } - - @Override - public boolean setState(int[] stateSet) { - if (mEnabled) { - return super.setState(stateSet); - } - return false; - } - - @Override - public void draw(Canvas canvas) { - if (mEnabled) { - super.draw(canvas); - } - } - - @Override - public void setHotspot(float x, float y) { - if (mEnabled) { - super.setHotspot(x, y); - } - } - - @Override - public void setHotspotBounds(int left, int top, int right, int bottom) { - if (mEnabled) { - super.setHotspotBounds(left, top, right, bottom); - } - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - if (mEnabled) { - return super.setVisible(visible, restart); - } - return false; - } - } -} diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java index a2879796..b195d3c0 100644 --- a/android/support/v7/widget/RecyclerView.java +++ b/android/support/v7/widget/RecyclerView.java @@ -2722,7 +2722,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro removeCallbacks(mItemAnimatorRunner); mViewInfoStore.onDetach(); - if (ALLOW_THREAD_GAP_WORK) { + if (ALLOW_THREAD_GAP_WORK && mGapWorker != null) { // Unregister with gap worker mGapWorker.remove(this); mGapWorker = null; @@ -6643,11 +6643,19 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * @see #onCreateViewHolder(ViewGroup, int) */ public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { - TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); - final VH holder = onCreateViewHolder(parent, viewType); - holder.mItemViewType = viewType; - TraceCompat.endSection(); - return holder; + try { + TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); + final VH holder = onCreateViewHolder(parent, viewType); + if (holder.itemView.getParent() != null) { + throw new IllegalStateException("ViewHolder views must not be attached when" + + " created. Ensure that you are not passing 'true' to the attachToRoot" + + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)"); + } + holder.mItemViewType = viewType; + return holder; + } finally { + TraceCompat.endSection(); + } } /** @@ -10108,7 +10116,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro if (vScroll == 0 && hScroll == 0) { return false; } - mRecyclerView.scrollBy(hScroll, vScroll); + mRecyclerView.smoothScrollBy(hScroll, vScroll); return true; } diff --git a/android/support/v7/widget/StaggeredGridLayoutManager.java b/android/support/v7/widget/StaggeredGridLayoutManager.java index 55fb14e8..4e560b45 100644 --- a/android/support/v7/widget/StaggeredGridLayoutManager.java +++ b/android/support/v7/widget/StaggeredGridLayoutManager.java @@ -16,6 +16,7 @@ package android.support.v7.widget; +import static android.support.annotation.RestrictTo.Scope.LIBRARY; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD; import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL; @@ -2069,6 +2070,7 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager imple /** @hide */ @Override + @RestrictTo(LIBRARY) public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, LayoutPrefetchRegistry layoutPrefetchRegistry) { /* This method uses the simplifying assumption that the next N items (where N = span count) diff --git a/android/support/v7/widget/TooltipCompatHandler.java b/android/support/v7/widget/TooltipCompatHandler.java index 63a61982..8de44e6f 100644 --- a/android/support/v7/widget/TooltipCompatHandler.java +++ b/android/support/v7/widget/TooltipCompatHandler.java @@ -22,6 +22,7 @@ import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; import android.content.Context; import android.support.annotation.RestrictTo; import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewConfigurationCompat; import android.text.TextUtils; import android.util.Log; import android.view.MotionEvent; @@ -46,6 +47,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList private final View mAnchor; private final CharSequence mTooltipText; + private final int mHoverSlop; private final Runnable mShowRunnable = new Runnable() { @Override @@ -104,6 +106,9 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList private TooltipCompatHandler(View anchor, CharSequence tooltipText) { mAnchor = anchor; mTooltipText = tooltipText; + mHoverSlop = ViewConfigurationCompat.getScaledHoverSlop( + ViewConfiguration.get(mAnchor.getContext())); + clearAnchorPos(); mAnchor.setOnLongClickListener(this); mAnchor.setOnHoverListener(this); @@ -129,13 +134,12 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList } switch (event.getAction()) { case MotionEvent.ACTION_HOVER_MOVE: - if (mAnchor.isEnabled() && mPopup == null) { - mAnchorX = (int) event.getX(); - mAnchorY = (int) event.getY(); + if (mAnchor.isEnabled() && mPopup == null && updateAnchorPos(event)) { setPendingHandler(this); } break; case MotionEvent.ACTION_HOVER_EXIT: + clearAnchorPos(); hide(); break; } @@ -188,6 +192,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList if (mPopup != null) { mPopup.hide(); mPopup = null; + clearAnchorPos(); mAnchor.removeOnAttachStateChangeListener(this); } else { Log.e(TAG, "sActiveHandler.mPopup == null"); @@ -216,4 +221,31 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList private void cancelPendingShow() { mAnchor.removeCallbacks(mShowRunnable); } + + /** + * Update the anchor position if it significantly (that is by at least mHoverSlope) + * different from the previously stored position. Ignoring insignificant changes + * filters out the jitter which is typical for such input sources as stylus. + * + * @return True if the position has been updated. + */ + private boolean updateAnchorPos(MotionEvent event) { + final int newAnchorX = (int) event.getX(); + final int newAnchorY = (int) event.getY(); + if (Math.abs(newAnchorX - mAnchorX) <= mHoverSlop + && Math.abs(newAnchorY - mAnchorY) <= mHoverSlop) { + return false; + } + mAnchorX = newAnchorX; + mAnchorY = newAnchorY; + return true; + } + + /** + * Clear the anchor position to ensure that the next change is considered significant. + */ + private void clearAnchorPos() { + mAnchorX = Integer.MAX_VALUE; + mAnchorY = Integer.MAX_VALUE; + } } diff --git a/android/support/wear/widget/BoxInsetLayout.java b/android/support/wear/widget/BoxInsetLayout.java index a8b13814..383bcb7d 100644 --- a/android/support/wear/widget/BoxInsetLayout.java +++ b/android/support/wear/widget/BoxInsetLayout.java @@ -46,7 +46,7 @@ import java.lang.annotation.RetentionPolicy; @UiThread public class BoxInsetLayout extends ViewGroup { - private static final float FACTOR = 0.146467f; //(1 - sqrt(2)/2)/2 + private static final float FACTOR = 0.146447f; //(1 - sqrt(2)/2)/2 private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START; private final int mScreenHeight; |