Home | History | Annotate | Download | only in allapps
      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 
     79     public FloatingHeaderView(@NonNull Context context) {
     80         this(context, null);
     81     }
     82 
     83     public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
     84         super(context, attrs);
     85     }
     86 
     87     @Override
     88     protected void onFinishInflate() {
     89         super.onFinishInflate();
     90         mTabLayout = findViewById(R.id.tabs);
     91     }
     92 
     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 
    103     private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
    104         if (old != updated && updated != null ) {
    105             updated.addOnScrollListener(mOnScrollListener);
    106         }
    107         return updated;
    108     }
    109 
    110     public void setMainActive(boolean active) {
    111         mCurrentRV = active ? mMainRV : mWorkRV;
    112         mMainRVActive = active;
    113     }
    114 
    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 
    125     private boolean canSnapAt(int currentScrollY) {
    126         return Math.abs(currentScrollY) <= mMaxTranslation;
    127     }
    128 
    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 
    153     protected void applyScroll(int uncappedY, int currentY) { }
    154 
    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 
    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 
    186     public boolean isExpanded() {
    187         return !mHeaderCollapsed;
    188     }
    189 
    190     @Override
    191     public void onAnimationUpdate(ValueAnimator animation) {
    192         mTranslationY = (Integer) animation.getAnimatedValue();
    193         apply();
    194     }
    195 
    196     @Override
    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
    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 
    225     private void calcOffset(Point p) {
    226         p.x = getLeft() - mCurrentRV.getLeft() - mParent.getLeft();
    227         p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
    228     }
    229 
    230     public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter) {
    231         setter.setViewAlpha(this, hasContent ? 1 : 0, LINEAR);
    232         allowTouchForwarding(hasContent);
    233     }
    234 
    235     protected void allowTouchForwarding(boolean allow) {
    236         mAllowTouchForwarding = allow;
    237     }
    238 
    239     public boolean hasVisibleContent() {
    240         return false;
    241     }
    242 
    243     @Override
    244     public boolean hasOverlappingRendering() {
    245         return false;
    246     }
    247 }
    248 
    249 
    250