Adding an interface to allow adding custom views in FloatingHeaderView

Bug: 109828640
Change-Id: I9bde5d4fab47eb3e5787bbb741b5b9051a15c0c2
This commit is contained in:
Sunny Goyal 2018-11-01 16:37:03 -07:00
parent 066ace1b88
commit dcf917b133
6 changed files with 351 additions and 103 deletions

View File

@ -30,7 +30,50 @@
layout="@layout/all_apps_rv_layout"
android:visibility="gone" />
<include layout="@layout/all_apps_floating_header" />
<com.android.launcher3.allapps.FloatingHeaderView
android:id="@+id/all_apps_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/search_container_all_apps"
android:clipToPadding="false"
android:paddingTop="@dimen/all_apps_header_top_padding"
android:orientation="vertical" >
<include layout="@layout/floating_header_content" />
<com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_header_tab_height"
android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
android:orientation="horizontal">
<Button
android:id="@+id/tab_personal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:fontFamily="sans-serif-medium"
android:text="@string/all_apps_personal_tab"
android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
<Button
android:id="@+id/tab_work"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:fontFamily="sans-serif-medium"
android:text="@string/all_apps_work_tab"
android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
</com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
</com.android.launcher3.allapps.FloatingHeaderView>
<include
android:id="@id/search_container_all_apps"

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 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.
-->
<com.android.launcher3.allapps.FloatingHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/all_apps_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/search_container_all_apps"
android:clipToPadding="false"
android:paddingTop="@dimen/all_apps_header_top_padding"
android:orientation="vertical" >
<com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_header_tab_height"
android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
android:orientation="horizontal">
<Button
android:id="@+id/tab_personal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:fontFamily="sans-serif-medium"
android:text="@string/all_apps_personal_tab"
android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
<Button
android:id="@+id/tab_work"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:fontFamily="sans-serif-medium"
android:text="@string/all_apps_work_tab"
android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
</com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
</com.android.launcher3.allapps.FloatingHeaderView>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 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.
-->
<merge />

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 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 com.android.launcher3.allapps;
import android.graphics.Rect;
import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.PropertySetter;
/**
* A abstract representation of a row in all-apps view
*/
public interface FloatingHeaderRow {
FloatingHeaderRow[] NO_ROWS = new FloatingHeaderRow[0];
void setup(FloatingHeaderView parent, FloatingHeaderRow[] allRows, boolean tabsHidden);
void setInsets(Rect insets, DeviceProfile grid);
int getExpectedHeight();
/**
* Returns true if the row should draw based on its current position and layout.
*/
boolean shouldDraw();
/**
* Returns true if the view has anything worth drawing. This is different than
* {@link #shouldDraw()} as this is called earlier in the layout to determine the view
* position.
*/
boolean hasVisibleContent();
void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
PropertySetter setter, Interpolator fadeInterpolator);
/**
* Scrolls the content vertically.
*/
void setVerticalScroll(int scroll, boolean isScrolledOut);
Class<? extends FloatingHeaderRow> getTypeClass();
}

View File

@ -19,28 +19,34 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.AllAppsRow;
import com.android.systemui.plugins.AllAppsRow.OnHeightUpdatedListener;
import com.android.systemui.plugins.PluginListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
public class FloatingHeaderView extends LinearLayout implements
ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow> {
ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow>, Insettable,
OnHeightUpdatedListener {
private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
@ -62,17 +68,18 @@ public class FloatingHeaderView extends LinearLayout implements
int current = -mCurrentRV.getCurrentScrollY();
moved(current);
apply();
applyVerticalMove();
}
};
private final int mHeaderTopPadding;
protected final Map<AllAppsRow, PluginHeaderRow> mPluginRows = new ArrayMap<>();
protected ViewGroup mTabLayout;
private AllAppsRecyclerView mMainRV;
private AllAppsRecyclerView mWorkRV;
private AllAppsRecyclerView mCurrentRV;
protected final Map<AllAppsRow, View> mPluginRows;
// Contains just the values of the above map so we can iterate without extracting a new list.
protected final List<View> mPluginRowViews;
private ViewGroup mParent;
private boolean mHeaderCollapsed;
private int mSnappedScrolledY;
@ -85,20 +92,42 @@ public class FloatingHeaderView extends LinearLayout implements
protected int mMaxTranslation;
private boolean mMainRVActive = true;
private boolean mCollapsed = false;
// This is initialized once during inflation and stays constant after that. Fixed views
// cannot be added or removed dynamically.
private FloatingHeaderRow[] mFixedRows = FloatingHeaderRow.NO_ROWS;
// Array of all fixed rows and plugin rows. This is initialized every time a plugin is
// enabled or disabled, and represent the current set of all rows.
private FloatingHeaderRow[] mAllRows = FloatingHeaderRow.NO_ROWS;
public FloatingHeaderView(@NonNull Context context) {
this(context, null);
}
public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPluginRows = new HashMap<>();
mPluginRowViews = new ArrayList<>();
mHeaderTopPadding = context.getResources()
.getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTabLayout = findViewById(R.id.tabs);
// Find all floating header rows.
ArrayList<FloatingHeaderRow> rows = new ArrayList<>();
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child instanceof FloatingHeaderRow) {
rows.add((FloatingHeaderRow) child);
}
}
mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
mAllRows = mFixedRows;
}
@Override
@ -114,50 +143,70 @@ public class FloatingHeaderView extends LinearLayout implements
PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
}
@Override
public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) {
mPluginRows.put(allAppsRowPlugin, null);
setupPluginRows();
allAppsRowPlugin.setOnHeightUpdatedListener(this::onPluginRowHeightUpdated);
private void recreateAllRowsArray() {
int pluginCount = mPluginRows.size();
if (pluginCount == 0) {
mAllRows = mFixedRows;
} else {
int count = mFixedRows.length;
mAllRows = new FloatingHeaderRow[count + pluginCount];
for (int i = 0; i < count; i++) {
mAllRows[i] = mFixedRows[i];
}
for (PluginHeaderRow row : mPluginRows.values()) {
mAllRows[count] = row;
count++;
}
}
}
protected void onPluginRowHeightUpdated() {
@Override
public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) {
PluginHeaderRow headerRow = new PluginHeaderRow(allAppsRowPlugin, this);
addView(headerRow.mView, indexOfChild(mTabLayout));
mPluginRows.put(allAppsRowPlugin, headerRow);
recreateAllRowsArray();
allAppsRowPlugin.setOnHeightUpdatedListener(this);
}
@Override
public void onHeightUpdated() {
int oldMaxHeight = mMaxTranslation;
updateExpectedHeight();
if (mMaxTranslation != oldMaxHeight) {
AllAppsContainerView parent = (AllAppsContainerView) getParent();
if (parent != null) {
parent.setupHeader();
}
}
}
@Override
public void onPluginDisconnected(AllAppsRow plugin) {
View pluginRowView = mPluginRows.get(plugin);
removeView(pluginRowView);
PluginHeaderRow row = mPluginRows.get(plugin);
removeView(row.mView);
mPluginRows.remove(plugin);
mPluginRowViews.remove(pluginRowView);
onPluginRowHeightUpdated();
recreateAllRowsArray();
onHeightUpdated();
}
public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
for (FloatingHeaderRow row : mAllRows) {
row.setup(this, mAllRows, tabsHidden);
}
updateExpectedHeight();
mTabsHidden = tabsHidden;
mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
mMainRV = setupRV(mMainRV, mAH[AllAppsContainerView.AdapterHolder.MAIN].recyclerView);
mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
mParent = (ViewGroup) mMainRV.getParent();
setMainActive(mMainRVActive || mWorkRV == null);
setupPluginRows();
reset(false);
}
private void setupPluginRows() {
for (Map.Entry<AllAppsRow, View> rowPluginEntry : mPluginRows.entrySet()) {
if (rowPluginEntry.getValue() == null) {
View pluginRow = rowPluginEntry.getKey().setup(this);
addView(pluginRow, indexOfChild(mTabLayout));
rowPluginEntry.setValue(pluginRow);
mPluginRowViews.add(pluginRow);
}
}
for (View plugin : mPluginRowViews) {
plugin.setVisibility(mHeaderCollapsed ? GONE : VISIBLE);
}
}
private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
if (old != updated && updated != null ) {
updated.addOnScrollListener(mOnScrollListener);
@ -165,6 +214,16 @@ public class FloatingHeaderView extends LinearLayout implements
return updated;
}
private void updateExpectedHeight() {
mMaxTranslation = 0;
if (mCollapsed) {
return;
}
for (FloatingHeaderRow row : mAllRows) {
mMaxTranslation += row.getExpectedHeight();
}
}
public void setMainActive(boolean active) {
mCurrentRV = active ? mMainRV : mWorkRV;
mMainRVActive = active;
@ -208,12 +267,21 @@ public class FloatingHeaderView extends LinearLayout implements
}
}
protected void applyScroll(int uncappedY, int currentY) { }
protected void apply() {
protected void applyVerticalMove() {
int uncappedTranslationY = mTranslationY;
mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
applyScroll(uncappedTranslationY, mTranslationY);
if (mCollapsed || uncappedTranslationY < mTranslationY - mHeaderTopPadding) {
// we hide it completely if already capped (for opening search anim)
for (FloatingHeaderRow row : mAllRows) {
row.setVerticalScroll(0, true /* isScrolledOut */);
}
} else {
for (FloatingHeaderRow row : mAllRows) {
row.setVerticalScroll(uncappedTranslationY, false /* isScrolledOut */);
}
}
mTabLayout.setTranslationY(mTranslationY);
mClip.top = mMaxTranslation + mTranslationY;
// clipping on a draw might cause additional redraw
@ -223,6 +291,16 @@ public class FloatingHeaderView extends LinearLayout implements
}
}
/**
* Hides all the floating rows
*/
public void setCollapsed(boolean collapse) {
if (mCollapsed == collapse) return;
mCollapsed = collapse;
onHeightUpdated();
}
public void reset(boolean animate) {
if (mAnimator.isStarted()) {
mAnimator.cancel();
@ -234,7 +312,7 @@ public class FloatingHeaderView extends LinearLayout implements
mAnimator.start();
} else {
mTranslationY = 0;
apply();
applyVerticalMove();
}
mHeaderCollapsed = false;
mSnappedScrolledY = -mMaxTranslation;
@ -248,7 +326,7 @@ public class FloatingHeaderView extends LinearLayout implements
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTranslationY = (Integer) animation.getAnimatedValue();
apply();
applyVerticalMove();
}
@Override
@ -287,8 +365,12 @@ public class FloatingHeaderView extends LinearLayout implements
public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter,
Interpolator fadeInterpolator) {
setter.setViewAlpha(this, hasContent ? 1 : 0, fadeInterpolator);
for (FloatingHeaderRow row : mAllRows) {
row.setContentVisibility(hasHeader, hasContent, setter, fadeInterpolator);
}
allowTouchForwarding(hasContent);
setter.setFloat(mTabLayout, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
}
protected void allowTouchForwarding(boolean allow) {
@ -296,6 +378,11 @@ public class FloatingHeaderView extends LinearLayout implements
}
public boolean hasVisibleContent() {
for (FloatingHeaderRow row : mAllRows) {
if (row.hasVisibleContent()) {
return true;
}
}
return false;
}
@ -303,6 +390,23 @@ public class FloatingHeaderView extends LinearLayout implements
public boolean hasOverlappingRendering() {
return false;
}
@Override
public void setInsets(Rect insets) {
DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
for (FloatingHeaderRow row : mAllRows) {
row.setInsets(insets, grid);
}
}
public <T extends FloatingHeaderRow> T findFixedRowByType(Class<T> type) {
for (FloatingHeaderRow row : mAllRows) {
if (row.getTypeClass() == type) {
return (T) row;
}
}
return null;
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 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 com.android.launcher3.allapps;
import static android.view.View.ALPHA;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.PropertySetter;
import com.android.systemui.plugins.AllAppsRow;
/**
* Wrapper over an {@link AllAppsRow} plugin with {@link FloatingHeaderRow} interface so that
* it can be easily added in {@link FloatingHeaderView}.
*/
public class PluginHeaderRow implements FloatingHeaderRow {
private final AllAppsRow mPlugin;
final View mView;
PluginHeaderRow(AllAppsRow plugin, FloatingHeaderView parent) {
mPlugin = plugin;
mView = mPlugin.setup(parent);
}
@Override
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] allRows,
boolean tabsHidden) { }
@Override
public void setInsets(Rect insets, DeviceProfile grid) { }
@Override
public int getExpectedHeight() {
return mPlugin.getExpectedHeight();
}
@Override
public boolean shouldDraw() {
return true;
}
@Override
public boolean hasVisibleContent() {
return true;
}
@Override
public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
PropertySetter setter, Interpolator fadeInterpolator) {
// Don't use setViewAlpha as we want to control the visibility ourselves.
setter.setFloat(mView, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
}
@Override
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
mView.setVisibility(isScrolledOut ? INVISIBLE : VISIBLE);
if (!isScrolledOut) {
mView.setTranslationY(scroll);
}
}
@Override
public Class<PluginHeaderRow> getTypeClass() {
return PluginHeaderRow.class;
}
}