Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2016 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 
     17 package com.android.setupwizardlib.view;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.graphics.Canvas;
     22 import android.graphics.RectF;
     23 import android.os.Build;
     24 import android.util.AttributeSet;
     25 import android.view.MotionEvent;
     26 import android.view.View;
     27 import android.view.WindowInsets;
     28 
     29 /**
     30  * This class provides sticky header functionality in a recycler view, to use with
     31  * SetupWizardIllustration. To use this, add a header tagged with "sticky". The header will continue
     32  * to be drawn when the sticky element hits the top of the view.
     33  *
     34  * <p>There are a few things to note:
     35  * <ol>
     36  *   <li>The view does not work well with padding. b/16190933
     37  *   <li>If fitsSystemWindows is true, then this will offset the sticking position by the height of
     38  *   the system decorations at the top of the screen.
     39  * </ol>
     40  */
     41 public class StickyHeaderRecyclerView extends HeaderRecyclerView {
     42 
     43     private View mSticky;
     44     private int mStatusBarInset = 0;
     45     private RectF mStickyRect = new RectF();
     46 
     47     public StickyHeaderRecyclerView(Context context) {
     48         super(context);
     49     }
     50 
     51     public StickyHeaderRecyclerView(Context context, AttributeSet attrs) {
     52         super(context, attrs);
     53     }
     54 
     55     public StickyHeaderRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
     56         super(context, attrs, defStyleAttr);
     57     }
     58 
     59     @Override
     60     protected void onLayout(boolean changed, int l, int t, int r, int b) {
     61         super.onLayout(changed, l, t, r, b);
     62         if (mSticky == null) {
     63             updateStickyView();
     64         }
     65         if (mSticky != null) {
     66             final View headerView = getHeader();
     67             if (headerView != null && headerView.getHeight() == 0) {
     68                 headerView.layout(0, -headerView.getMeasuredHeight(),
     69                         headerView.getMeasuredWidth(), 0);
     70             }
     71         }
     72     }
     73 
     74     @Override
     75     protected void onMeasure(int widthSpec, int heightSpec) {
     76         super.onMeasure(widthSpec, heightSpec);
     77         if (mSticky != null) {
     78             measureChild(getHeader(), widthSpec, heightSpec);
     79         }
     80     }
     81 
     82     public void updateStickyView() {
     83         final View header = getHeader();
     84         if (header != null) {
     85             mSticky = header.findViewWithTag("sticky");
     86         }
     87     }
     88 
     89     @Override
     90     public void draw(Canvas canvas) {
     91         super.draw(canvas);
     92         if (mSticky != null) {
     93             final View headerView = getHeader();
     94             final int saveCount = canvas.save();
     95             // The view to draw when sticking to the top
     96             final View drawTarget = headerView != null ? headerView : mSticky;
     97             // The offset to draw the view at when sticky
     98             final int drawOffset = headerView != null ? mSticky.getTop() : 0;
     99             // Position of the draw target, relative to the outside of the scrollView
    100             final int drawTop = drawTarget.getTop();
    101             if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) {
    102                 // RecyclerView does not translate the canvas, so we can simply draw at the top
    103                 mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(),
    104                         drawTarget.getHeight() - drawOffset + mStatusBarInset);
    105                 canvas.translate(0, mStickyRect.top);
    106                 canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight());
    107                 drawTarget.draw(canvas);
    108             } else {
    109                 mStickyRect.setEmpty();
    110             }
    111             canvas.restoreToCount(saveCount);
    112         }
    113     }
    114 
    115     @Override
    116     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    117     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    118         if (getFitsSystemWindows()) {
    119             mStatusBarInset = insets.getSystemWindowInsetTop();
    120             insets.replaceSystemWindowInsets(
    121                     insets.getSystemWindowInsetLeft(),
    122                     0, /* top */
    123                     insets.getSystemWindowInsetRight(),
    124                     insets.getSystemWindowInsetBottom()
    125             );
    126         }
    127         return insets;
    128     }
    129 
    130     @Override
    131     public boolean dispatchTouchEvent(MotionEvent ev) {
    132         if (mStickyRect.contains(ev.getX(), ev.getY())) {
    133             ev.offsetLocation(-mStickyRect.left, -mStickyRect.top);
    134             return getHeader().dispatchTouchEvent(ev);
    135         } else {
    136             return super.dispatchTouchEvent(ev);
    137         }
    138     }
    139 }
    140