StickyHeaderScrollView.java revision 2646e1d82ec6d133b35b775f044e156fca6d9d75
1/* 2 * Copyright (C) 2015 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 17package com.android.setupwizardlib.view; 18 19import android.annotation.TargetApi; 20import android.content.Context; 21import android.graphics.Canvas; 22import android.graphics.RectF; 23import android.os.Build; 24import android.util.AttributeSet; 25import android.view.MotionEvent; 26import android.view.View; 27import android.view.WindowInsets; 28 29/** 30 * This class provides sticky header functionality in a scroll view, to use with 31 * SetupWizardIllustration. To use this, add a subview tagged with "sticky", or a subview tagged 32 * with "stickyContainer" and one of its child tagged as "sticky". The sticky container will be 33 * drawn when the sticky element hits the top of the view. 34 * 35 * There are a few things to note: 36 * 1. The two supported scenarios are StickyHeaderScrollView -> subview (stickyContainer) -> sticky, 37 * and StickyHeaderScrollView -> container -> subview (sticky). 38 * The arrow (->) represents parent/child relationship and must be immediate child. 39 * 2. The view does not work well with padding. b/16190933 40 * 3. If fitsSystemWindows is true, then this will offset the sticking position by the height of 41 * the system decorations at the top of the screen. 42 * 43 * @see StickyHeaderListView 44 */ 45public class StickyHeaderScrollView extends BottomScrollView { 46 47 private View mSticky; 48 private View mStickyContainer; 49 private int mStatusBarInset = 0; 50 private RectF mStickyRect = new RectF(); 51 52 public StickyHeaderScrollView(Context context) { 53 super(context); 54 } 55 56 public StickyHeaderScrollView(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 } 59 60 public StickyHeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 61 super(context, attrs, defStyleAttr); 62 } 63 64 @Override 65 protected void onLayout(boolean changed, int l, int t, int r, int b) { 66 super.onLayout(changed, l, t, r, b); 67 if (mSticky == null) { 68 updateStickyView(); 69 } 70 } 71 72 public void updateStickyView() { 73 mSticky = findViewWithTag("sticky"); 74 mStickyContainer = findViewWithTag("stickyContainer"); 75 } 76 77 @Override 78 public boolean dispatchTouchEvent(MotionEvent ev) { 79 if (mStickyRect.contains(ev.getX(), ev.getY())) { 80 ev.offsetLocation(-mStickyRect.left, -mStickyRect.top); 81 return mStickyContainer.dispatchTouchEvent(ev); 82 } else { 83 return super.dispatchTouchEvent(ev); 84 } 85 } 86 87 @Override 88 public void draw(Canvas canvas) { 89 super.draw(canvas); 90 if (mSticky != null) { 91 final int saveCount = canvas.save(); 92 // The view to draw when sticking to the top 93 final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky; 94 // The offset to draw the view at when sticky 95 final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0; 96 // Position of the draw target, relative to the outside of the scrollView 97 final int drawTop = drawTarget.getTop() - getScrollY(); 98 if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) { 99 // ScrollView translates the whole canvas so we have to compensate for that 100 mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(), 101 drawTarget.getHeight() - drawOffset + mStatusBarInset); 102 canvas.translate(0, -drawTop + mStickyRect.top); 103 canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight()); 104 drawTarget.draw(canvas); 105 } else { 106 mStickyRect.setEmpty(); 107 } 108 canvas.restoreToCount(saveCount); 109 } 110 onDrawScrollBars(canvas); 111 } 112 113 @Override 114 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 115 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 116 if (getFitsSystemWindows()) { 117 mStatusBarInset = insets.getSystemWindowInsetTop(); 118 insets = insets.replaceSystemWindowInsets( 119 insets.getSystemWindowInsetLeft(), 120 0, /* top */ 121 insets.getSystemWindowInsetRight(), 122 insets.getSystemWindowInsetBottom() 123 ); 124 } 125 return insets; 126 } 127} 128