2017-10-18 10:31:36 -07:00
|
|
|
/*
|
|
|
|
|
* 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.launcher3.allapps;
|
|
|
|
|
|
2017-12-06 11:45:49 -08:00
|
|
|
|
2017-11-14 11:32:00 -08:00
|
|
|
import android.animation.ValueAnimator;
|
2017-12-06 11:45:49 -08:00
|
|
|
import android.content.Context;
|
2017-11-14 11:32:00 -08:00
|
|
|
import android.content.res.Resources;
|
2017-10-18 10:31:36 -07:00
|
|
|
import android.graphics.Rect;
|
|
|
|
|
import android.support.annotation.NonNull;
|
|
|
|
|
import android.support.annotation.Nullable;
|
|
|
|
|
import android.support.v7.widget.RecyclerView;
|
2017-12-06 11:45:49 -08:00
|
|
|
import android.util.AttributeSet;
|
2017-10-18 10:31:36 -07:00
|
|
|
import android.view.View;
|
2017-11-14 11:32:00 -08:00
|
|
|
import android.view.ViewGroup;
|
|
|
|
|
import android.widget.RelativeLayout;
|
2017-10-18 10:31:36 -07:00
|
|
|
|
|
|
|
|
import com.android.launcher3.R;
|
|
|
|
|
|
2017-12-06 11:45:49 -08:00
|
|
|
public class FloatingHeaderView extends RelativeLayout implements
|
|
|
|
|
ValueAnimator.AnimatorUpdateListener {
|
2017-10-18 10:31:36 -07:00
|
|
|
|
2017-11-27 13:10:44 -08:00
|
|
|
private static final boolean SHOW_PREDICTIONS_ONLY_ON_TOP = true;
|
|
|
|
|
|
2017-10-18 10:31:36 -07:00
|
|
|
private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
|
2017-11-14 11:32:00 -08:00
|
|
|
private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
|
2017-12-06 11:45:49 -08:00
|
|
|
private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
|
|
|
|
if (SHOW_PREDICTIONS_ONLY_ON_TOP) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!mTopOnlyMode && newState == RecyclerView.SCROLL_STATE_IDLE
|
|
|
|
|
&& mTranslationY != -mMaxTranslation && mTranslationY != 0) {
|
|
|
|
|
float scroll = Math.abs(getCurrentScroll());
|
|
|
|
|
boolean expand = scroll > mMaxTranslation
|
|
|
|
|
? Math.abs(mTranslationY) < mMaxTranslation / 2 : true;
|
|
|
|
|
setExpanded(expand);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onScrolled(RecyclerView rv, int dx, int dy) {
|
|
|
|
|
boolean isMainRV = rv == mMainRV;
|
|
|
|
|
if (isMainRV != mMainRVActive) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mAnimator.isStarted()) {
|
|
|
|
|
mAnimator.cancel();
|
|
|
|
|
}
|
2017-10-18 10:31:36 -07:00
|
|
|
|
2017-12-06 11:45:49 -08:00
|
|
|
int current = - (isMainRV
|
|
|
|
|
? mMainRV.getCurrentScrollY()
|
|
|
|
|
: mWorkRV.getCurrentScrollY());
|
|
|
|
|
moved(current);
|
|
|
|
|
apply();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private PredictionRowView mPredictionRow;
|
|
|
|
|
private ViewGroup mTabLayout;
|
|
|
|
|
private View mDivider;
|
2017-11-27 13:10:44 -08:00
|
|
|
private AllAppsRecyclerView mMainRV;
|
|
|
|
|
private AllAppsRecyclerView mWorkRV;
|
2017-11-14 11:32:00 -08:00
|
|
|
private boolean mTopOnlyMode;
|
2017-10-18 10:31:36 -07:00
|
|
|
private boolean mHeaderHidden;
|
2017-11-14 11:32:00 -08:00
|
|
|
private int mMaxTranslation;
|
2017-10-18 10:31:36 -07:00
|
|
|
private int mSnappedScrolledY;
|
|
|
|
|
private int mTranslationY;
|
|
|
|
|
private int mMainScrolledY;
|
|
|
|
|
private int mWorkScrolledY;
|
|
|
|
|
private boolean mMainRVActive;
|
|
|
|
|
|
2017-12-06 11:45:49 -08:00
|
|
|
public FloatingHeaderView(@NonNull Context context) {
|
|
|
|
|
this(context, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
|
|
|
super(context, attrs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onFinishInflate() {
|
|
|
|
|
super.onFinishInflate();
|
|
|
|
|
mTabLayout = findViewById(R.id.tabs);
|
|
|
|
|
mDivider = findViewById(R.id.divider);
|
|
|
|
|
mPredictionRow = findViewById(R.id.header_content);
|
2017-11-14 11:32:00 -08:00
|
|
|
}
|
|
|
|
|
|
2017-11-27 13:10:44 -08:00
|
|
|
public void setup(@NonNull AllAppsRecyclerView personalRV, @Nullable AllAppsRecyclerView workRV,
|
2017-12-06 11:45:49 -08:00
|
|
|
int predictionRowHeight) {
|
2017-11-14 11:32:00 -08:00
|
|
|
mTopOnlyMode = workRV == null;
|
|
|
|
|
mTabLayout.setVisibility(mTopOnlyMode ? View.GONE : View.VISIBLE);
|
|
|
|
|
mPredictionRow.getLayoutParams().height = predictionRowHeight;
|
|
|
|
|
mMaxTranslation = predictionRowHeight;
|
2017-11-27 13:10:44 -08:00
|
|
|
mMainRV = setupRV(mMainRV, personalRV);
|
|
|
|
|
mWorkRV = setupRV(mWorkRV, workRV);
|
2017-10-18 10:31:36 -07:00
|
|
|
setMainActive(true);
|
2017-11-14 11:32:00 -08:00
|
|
|
setupDivider();
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-27 13:10:44 -08:00
|
|
|
private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
|
|
|
|
|
if (old != updated && updated != null ) {
|
2017-12-06 11:45:49 -08:00
|
|
|
updated.addOnScrollListener(mOnScrollListener);
|
2017-11-27 13:10:44 -08:00
|
|
|
}
|
|
|
|
|
return updated;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-14 11:32:00 -08:00
|
|
|
private void setupDivider() {
|
2017-12-06 11:45:49 -08:00
|
|
|
Resources res = getResources();
|
2017-11-14 11:32:00 -08:00
|
|
|
int verticalGap = res.getDimensionPixelSize(R.dimen.all_apps_divider_margin_vertical);
|
|
|
|
|
int sideGap = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
|
|
|
|
|
mDivider.setPadding(sideGap, verticalGap,sideGap, mTopOnlyMode ? verticalGap : 0);
|
|
|
|
|
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mDivider.getLayoutParams();
|
|
|
|
|
lp.removeRule(RelativeLayout.ALIGN_BOTTOM);
|
|
|
|
|
lp.addRule(RelativeLayout.ALIGN_BOTTOM, mTopOnlyMode ? R.id.header_content : R.id.tabs);
|
|
|
|
|
mDivider.setLayoutParams(lp);
|
2017-10-18 10:31:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setMainActive(boolean active) {
|
|
|
|
|
mMainRVActive = active;
|
|
|
|
|
mSnappedScrolledY = getCurrentScroll() - mMaxTranslation;
|
|
|
|
|
setExpanded(true);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-06 11:45:49 -08:00
|
|
|
public PredictionRowView getPredictionRow() {
|
2017-11-14 11:32:00 -08:00
|
|
|
return mPredictionRow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ViewGroup getTabLayout() {
|
|
|
|
|
return mTabLayout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public View getDivider() {
|
|
|
|
|
return mDivider;
|
2017-10-18 10:31:36 -07:00
|
|
|
}
|
|
|
|
|
|
2017-11-14 11:32:00 -08:00
|
|
|
public void reset() {
|
|
|
|
|
mMainScrolledY = 0;
|
|
|
|
|
mWorkScrolledY = 0;
|
|
|
|
|
setExpanded(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean canSnapAt(int currentScrollY) {
|
2017-11-27 13:10:44 -08:00
|
|
|
boolean snapOnlyOnTop = SHOW_PREDICTIONS_ONLY_ON_TOP || mTopOnlyMode;
|
|
|
|
|
return !snapOnlyOnTop || Math.abs(currentScrollY) <= mPredictionRow.getHeight();
|
2017-10-18 10:31:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void moved(final int currentScrollY) {
|
|
|
|
|
if (mHeaderHidden) {
|
|
|
|
|
if (currentScrollY <= mSnappedScrolledY) {
|
2017-11-14 11:32:00 -08:00
|
|
|
if (canSnapAt(currentScrollY)) {
|
|
|
|
|
mSnappedScrolledY = currentScrollY;
|
|
|
|
|
}
|
2017-10-18 10:31:36 -07:00
|
|
|
} else {
|
|
|
|
|
mHeaderHidden = false;
|
|
|
|
|
}
|
|
|
|
|
mTranslationY = currentScrollY;
|
2017-11-14 11:32:00 -08:00
|
|
|
} else if (!mHeaderHidden) {
|
2017-10-18 10:31:36 -07:00
|
|
|
mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
|
|
|
|
|
|
|
|
|
|
// update state vars
|
|
|
|
|
if (mTranslationY >= 0) { // expanded: must not move down further
|
|
|
|
|
mTranslationY = 0;
|
|
|
|
|
mSnappedScrolledY = currentScrollY - mMaxTranslation;
|
|
|
|
|
} else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
|
|
|
|
|
mHeaderHidden = true;
|
|
|
|
|
mSnappedScrolledY = currentScrollY;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void apply() {
|
2017-11-14 11:32:00 -08:00
|
|
|
int uncappedTranslationY = mTranslationY;
|
2017-10-18 10:31:36 -07:00
|
|
|
mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
|
2017-11-28 11:59:08 -08:00
|
|
|
if (mTranslationY != uncappedTranslationY) {
|
|
|
|
|
// we hide it completely if already capped (for opening search anim)
|
|
|
|
|
mPredictionRow.setVisibility(View.INVISIBLE);
|
|
|
|
|
} else {
|
|
|
|
|
mPredictionRow.setVisibility(View.VISIBLE);
|
|
|
|
|
mPredictionRow.setTranslationY(uncappedTranslationY);
|
|
|
|
|
}
|
2017-11-14 11:32:00 -08:00
|
|
|
mTabLayout.setTranslationY(mTranslationY);
|
|
|
|
|
mDivider.setTranslationY(mTopOnlyMode ? uncappedTranslationY : mTranslationY);
|
2017-10-18 10:31:36 -07:00
|
|
|
mClip.top = mMaxTranslation + mTranslationY;
|
2017-11-14 11:32:00 -08:00
|
|
|
// clipping on a draw might cause additional redraw
|
2017-10-18 10:31:36 -07:00
|
|
|
mMainRV.setClipBounds(mClip);
|
|
|
|
|
if (mWorkRV != null) {
|
|
|
|
|
mWorkRV.setClipBounds(mClip);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void setExpanded(boolean expand) {
|
|
|
|
|
int translateTo = expand ? 0 : -mMaxTranslation;
|
2017-11-14 11:32:00 -08:00
|
|
|
mAnimator.setIntValues(mTranslationY, translateTo);
|
|
|
|
|
mAnimator.addUpdateListener(this);
|
|
|
|
|
mAnimator.setDuration(150);
|
|
|
|
|
mAnimator.start();
|
2017-10-18 10:31:36 -07:00
|
|
|
mHeaderHidden = !expand;
|
|
|
|
|
mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean isExpanded() {
|
|
|
|
|
return !mHeaderHidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int getCurrentScroll() {
|
|
|
|
|
return mMainRVActive ? mMainScrolledY : mWorkScrolledY;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-14 11:32:00 -08:00
|
|
|
@Override
|
|
|
|
|
public void onAnimationUpdate(ValueAnimator animation) {
|
|
|
|
|
mTranslationY = (Integer) animation.getAnimatedValue();
|
|
|
|
|
apply();
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 10:31:36 -07:00
|
|
|
}
|
2017-12-06 11:45:49 -08:00
|
|
|
|
|
|
|
|
|