• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.allapps;
17 
18 import static com.android.launcher3.anim.Interpolators.LINEAR;
19 
20 import android.animation.ValueAnimator;
21 import android.content.Context;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.support.annotation.NonNull;
25 import android.support.annotation.Nullable;
26 import android.support.v7.widget.RecyclerView;
27 import android.util.AttributeSet;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.LinearLayout;
32 
33 import com.android.launcher3.R;
34 import com.android.launcher3.anim.PropertySetter;
35 
36 public class FloatingHeaderView extends LinearLayout implements
37         ValueAnimator.AnimatorUpdateListener {
38 
39     private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
40     private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
41     private final Point mTempOffset = new Point();
42     private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
43         @Override
44         public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
45         }
46 
47         @Override
48         public void onScrolled(RecyclerView rv, int dx, int dy) {
49             if (rv != mCurrentRV) {
50                 return;
51             }
52 
53             if (mAnimator.isStarted()) {
54                 mAnimator.cancel();
55             }
56 
57             int current = -mCurrentRV.getCurrentScrollY();
58             moved(current);
59             apply();
60         }
61     };
62 
63     protected ViewGroup mTabLayout;
64     private AllAppsRecyclerView mMainRV;
65     private AllAppsRecyclerView mWorkRV;
66     private AllAppsRecyclerView mCurrentRV;
67     private ViewGroup mParent;
68     private boolean mHeaderCollapsed;
69     private int mSnappedScrolledY;
70     private int mTranslationY;
71 
72     private boolean mAllowTouchForwarding;
73     private boolean mForwardToRecyclerView;
74 
75     protected boolean mTabsHidden;
76     protected int mMaxTranslation;
77     private boolean mMainRVActive = true;
78 
FloatingHeaderView(@onNull Context context)79     public FloatingHeaderView(@NonNull Context context) {
80         this(context, null);
81     }
82 
FloatingHeaderView(@onNull Context context, @Nullable AttributeSet attrs)83     public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
84         super(context, attrs);
85     }
86 
87     @Override
onFinishInflate()88     protected void onFinishInflate() {
89         super.onFinishInflate();
90         mTabLayout = findViewById(R.id.tabs);
91     }
92 
setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden)93     public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
94         mTabsHidden = tabsHidden;
95         mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
96         mMainRV = setupRV(mMainRV, mAH[AllAppsContainerView.AdapterHolder.MAIN].recyclerView);
97         mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
98         mParent = (ViewGroup) mMainRV.getParent();
99         setMainActive(mMainRVActive || mWorkRV == null);
100         reset(false);
101     }
102 
setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated)103     private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
104         if (old != updated && updated != null ) {
105             updated.addOnScrollListener(mOnScrollListener);
106         }
107         return updated;
108     }
109 
setMainActive(boolean active)110     public void setMainActive(boolean active) {
111         mCurrentRV = active ? mMainRV : mWorkRV;
112         mMainRVActive = active;
113     }
114 
getMaxTranslation()115     public int getMaxTranslation() {
116         if (mMaxTranslation == 0 && mTabsHidden) {
117             return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
118         } else if (mMaxTranslation > 0 && mTabsHidden) {
119             return mMaxTranslation + getPaddingTop();
120         } else {
121             return mMaxTranslation;
122         }
123     }
124 
canSnapAt(int currentScrollY)125     private boolean canSnapAt(int currentScrollY) {
126         return Math.abs(currentScrollY) <= mMaxTranslation;
127     }
128 
moved(final int currentScrollY)129     private void moved(final int currentScrollY) {
130         if (mHeaderCollapsed) {
131             if (currentScrollY <= mSnappedScrolledY) {
132                 if (canSnapAt(currentScrollY)) {
133                     mSnappedScrolledY = currentScrollY;
134                 }
135             } else {
136                 mHeaderCollapsed = false;
137             }
138             mTranslationY = currentScrollY;
139         } else if (!mHeaderCollapsed) {
140             mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
141 
142             // update state vars
143             if (mTranslationY >= 0) { // expanded: must not move down further
144                 mTranslationY = 0;
145                 mSnappedScrolledY = currentScrollY - mMaxTranslation;
146             } else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
147                 mHeaderCollapsed = true;
148                 mSnappedScrolledY = -mMaxTranslation;
149             }
150         }
151     }
152 
applyScroll(int uncappedY, int currentY)153     protected void applyScroll(int uncappedY, int currentY) { }
154 
apply()155     protected void apply() {
156         int uncappedTranslationY = mTranslationY;
157         mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
158         applyScroll(uncappedTranslationY, mTranslationY);
159         mTabLayout.setTranslationY(mTranslationY);
160         mClip.top = mMaxTranslation + mTranslationY;
161         // clipping on a draw might cause additional redraw
162         mMainRV.setClipBounds(mClip);
163         if (mWorkRV != null) {
164             mWorkRV.setClipBounds(mClip);
165         }
166     }
167 
reset(boolean animate)168     public void reset(boolean animate) {
169         if (mAnimator.isStarted()) {
170             mAnimator.cancel();
171         }
172         if (animate) {
173             mAnimator.setIntValues(mTranslationY, 0);
174             mAnimator.addUpdateListener(this);
175             mAnimator.setDuration(150);
176             mAnimator.start();
177         } else {
178             mTranslationY = 0;
179             apply();
180         }
181         mHeaderCollapsed = false;
182         mSnappedScrolledY = -mMaxTranslation;
183         mCurrentRV.scrollToTop();
184     }
185 
isExpanded()186     public boolean isExpanded() {
187         return !mHeaderCollapsed;
188     }
189 
190     @Override
onAnimationUpdate(ValueAnimator animation)191     public void onAnimationUpdate(ValueAnimator animation) {
192         mTranslationY = (Integer) animation.getAnimatedValue();
193         apply();
194     }
195 
196     @Override
onInterceptTouchEvent(MotionEvent ev)197     public boolean onInterceptTouchEvent(MotionEvent ev) {
198         if (!mAllowTouchForwarding) {
199             mForwardToRecyclerView = false;
200             return super.onInterceptTouchEvent(ev);
201         }
202         calcOffset(mTempOffset);
203         ev.offsetLocation(mTempOffset.x, mTempOffset.y);
204         mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
205         ev.offsetLocation(-mTempOffset.x, -mTempOffset.y);
206         return mForwardToRecyclerView || super.onInterceptTouchEvent(ev);
207     }
208 
209     @Override
onTouchEvent(MotionEvent event)210     public boolean onTouchEvent(MotionEvent event) {
211         if (mForwardToRecyclerView) {
212             // take this view's and parent view's (view pager) location into account
213             calcOffset(mTempOffset);
214             event.offsetLocation(mTempOffset.x, mTempOffset.y);
215             try {
216                 return mCurrentRV.onTouchEvent(event);
217             } finally {
218                 event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
219             }
220         } else {
221             return super.onTouchEvent(event);
222         }
223     }
224 
calcOffset(Point p)225     private void calcOffset(Point p) {
226         p.x = getLeft() - mCurrentRV.getLeft() - mParent.getLeft();
227         p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
228     }
229 
setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter)230     public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter) {
231         setter.setViewAlpha(this, hasContent ? 1 : 0, LINEAR);
232         allowTouchForwarding(hasContent);
233     }
234 
allowTouchForwarding(boolean allow)235     protected void allowTouchForwarding(boolean allow) {
236         mAllowTouchForwarding = allow;
237     }
238 
hasVisibleContent()239     public boolean hasVisibleContent() {
240         return false;
241     }
242 
243     @Override
hasOverlappingRendering()244     public boolean hasOverlappingRendering() {
245         return false;
246     }
247 }
248 
249 
250