Show shadow when entity header starts scrolling.

- Add a controller to manage add/remove onScrollChangedListener to
  recyclerviews.
- When recyclerview on each screen is scrolled to top, set actionbar
  elevation to 0, otherwise set it to non-zero.
- When screen is moved to background, detach the listener.
- Use the controller in entity header.

Change-Id: Iecf194d885098c98c392810f62893ae9189f3936
Fix: 37670670
Test: make RunSettingsRoboTests
This commit is contained in:
Fan Zhang
2017-05-22 09:50:38 -07:00
parent 7983dba5f6
commit 841d1d5aab
18 changed files with 277 additions and 57 deletions

View File

@@ -0,0 +1,97 @@
/*
* 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 com.android.settings.widget;
import android.app.ActionBar;
import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
public class ActionBarShadowController implements LifecycleObserver, OnStart, OnStop {
private ScrollChangeWatcher mScrollChangeWatcher;
private RecyclerView mRecyclerView;
private boolean isScrollWatcherAttached;
public static ActionBarShadowController attachToRecyclerView(Activity activity,
Lifecycle lifecycle, RecyclerView recyclerView) {
return new ActionBarShadowController(activity, lifecycle, recyclerView);
}
private ActionBarShadowController(Activity activity, Lifecycle lifecycle,
RecyclerView recyclerView) {
mScrollChangeWatcher = new ScrollChangeWatcher(activity);
mRecyclerView = recyclerView;
attachScrollWatcher();
lifecycle.addObserver(this);
}
@Override
public void onStop() {
detachScrollWatcher();
}
private void detachScrollWatcher() {
mRecyclerView.removeOnScrollListener(mScrollChangeWatcher);
isScrollWatcherAttached = false;
}
@Override
public void onStart() {
attachScrollWatcher();
}
private void attachScrollWatcher() {
if (!isScrollWatcherAttached) {
isScrollWatcherAttached = true;
mRecyclerView.addOnScrollListener(mScrollChangeWatcher);
mScrollChangeWatcher.updateDropShadow(mRecyclerView);
}
}
/**
* Update the drop shadow as the scrollable entity is scrolled.
*/
private final class ScrollChangeWatcher extends RecyclerView.OnScrollListener {
private Activity mActivity;
public ScrollChangeWatcher(Activity activity) {
mActivity = activity;
}
// RecyclerView scrolled.
@Override
public void onScrolled(RecyclerView view, int dx, int dy) {
updateDropShadow(view);
}
public void updateDropShadow(View view) {
final boolean shouldShowShadow = view.canScrollVertically(-1);
final ActionBar actionBar = mActivity.getActionBar();
if (actionBar != null) {
actionBar.setElevation(shouldShowShadow ? 8 : 0);
}
}
}
}

View File

@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.support.annotation.IntDef;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -46,6 +47,7 @@ import com.android.settings.applications.InstalledAppDetails;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -68,11 +70,13 @@ public class EntityHeaderController {
private static final String TAG = "AppDetailFeature";
private final Context mContext;
private final Context mAppContext;
private final Activity mActivity;
private final Fragment mFragment;
private final int mMetricsCategory;
private final View mHeader;
private Lifecycle mLifecycle;
private RecyclerView mRecyclerView;
private Drawable mIcon;
private CharSequence mLabel;
private CharSequence mSummary;
@@ -93,15 +97,16 @@ public class EntityHeaderController {
* @param fragment The fragment that header will be placed in.
* @param header Optional: header view if it's already created.
*/
public static EntityHeaderController newInstance(Context context, Fragment fragment,
public static EntityHeaderController newInstance(Activity activity, Fragment fragment,
View header) {
return new EntityHeaderController(context.getApplicationContext(), fragment, header);
return new EntityHeaderController(activity, fragment, header);
}
private EntityHeaderController(Context context, Fragment fragment, View header) {
mContext = context;
private EntityHeaderController(Activity activity, Fragment fragment, View header) {
mActivity = activity;
mAppContext = activity.getApplicationContext();
mFragment = fragment;
mMetricsCategory = FeatureFactory.getFactory(context).getMetricsFeatureProvider()
mMetricsCategory = FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider()
.getMetricsCategory(fragment);
if (header != null) {
mHeader = header;
@@ -111,16 +116,22 @@ public class EntityHeaderController {
}
}
public EntityHeaderController setRecyclerView(RecyclerView recyclerView, Lifecycle lifecycle) {
mRecyclerView = recyclerView;
mLifecycle = lifecycle;
return this;
}
public EntityHeaderController setIcon(Drawable icon) {
if (icon != null) {
mIcon = icon.getConstantState().newDrawable(mContext.getResources());
mIcon = icon.getConstantState().newDrawable(mAppContext.getResources());
}
return this;
}
public EntityHeaderController setIcon(ApplicationsState.AppEntry appEntry) {
if (appEntry.icon != null) {
mIcon = appEntry.icon.getConstantState().newDrawable(mContext.getResources());
mIcon = appEntry.icon.getConstantState().newDrawable(mAppContext.getResources());
}
return this;
}
@@ -233,6 +244,9 @@ public class EntityHeaderController {
actionBar.setBackgroundDrawable(
new ColorDrawable(Utils.getColorAttr(activity, android.R.attr.colorSecondary)));
actionBar.setElevation(0);
if (mRecyclerView != null && mLifecycle != null) {
ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
}
return this;
}
@@ -257,7 +271,7 @@ public class EntityHeaderController {
button.setVisibility(View.GONE);
} else {
button.setContentDescription(
mContext.getString(R.string.application_info_label));
mAppContext.getString(R.string.application_info_label));
button.setImageResource(com.android.settings.R.drawable.ic_info);
button.setOnClickListener(new View.OnClickListener() {
@Override
@@ -311,7 +325,7 @@ public class EntityHeaderController {
}
private Intent resolveIntent(Intent i) {
ResolveInfo result = mContext.getPackageManager().resolveActivity(i, 0);
ResolveInfo result = mAppContext.getPackageManager().resolveActivity(i, 0);
if (result != null) {
return new Intent(i.getAction())
.setClassName(result.activityInfo.packageName, result.activityInfo.name);