1efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam/* 2efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * Copyright (C) 2015 The Android Open Source Project 3efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 4efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * Licensed under the Apache License, Version 2.0 (the "License"); 5efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * you may not use this file except in compliance with the License. 6efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * You may obtain a copy of the License at 7efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 8efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * http://www.apache.org/licenses/LICENSE-2.0 9efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 10efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * Unless required by applicable law or agreed to in writing, software 11efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * distributed under the License is distributed on an "AS IS" BASIS, 12efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * See the License for the specific language governing permissions and 14efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * limitations under the License. 15efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam */ 16efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 17efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lampackage com.android.setupwizardlib.view; 18efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 19efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.annotation.TargetApi; 20efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.content.Context; 21efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.content.res.TypedArray; 22efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.graphics.Canvas; 23efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.graphics.RectF; 24efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.os.Build; 25efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.util.AttributeSet; 26efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.LayoutInflater; 27efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.MotionEvent; 28efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.View; 29efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.WindowInsets; 302f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lamimport android.view.accessibility.AccessibilityEvent; 31efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.widget.ListView; 32efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 33efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport com.android.setupwizardlib.R; 34efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 35efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam/** 36efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * This class provides sticky header functionality in a list view, to use with 37efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * SetupWizardIllustration. To use this, add a header tagged with "sticky", or a header tagged with 38efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * "stickyContainer" and one of its child tagged as "sticky". The sticky container will be drawn 39efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * when the sticky element hits the top of the view. 40efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 41b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * <p>There are a few things to note: 42b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * <ol> 43b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * <li>The two supported scenarios are StickyHeaderListView -> Header (stickyContainer) -> sticky, 44b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * and StickyHeaderListView -> Header (sticky). The arrow (->) represents parent/child 45b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * relationship and must be immediate child. 46b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * <li>The view does not work well with padding. b/16190933 47b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * <li>If fitsSystemWindows is true, then this will offset the sticking position by the height of 48b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * the system decorations at the top of the screen. 49b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * </ol> 50efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 51efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * @see StickyHeaderScrollView 52efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam */ 53efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lampublic class StickyHeaderListView extends ListView { 54efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 55efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private View mSticky; 56efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private View mStickyContainer; 57efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private int mStatusBarInset = 0; 58efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private RectF mStickyRect = new RectF(); 59efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 60efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public StickyHeaderListView(Context context) { 619f9367672191190f903955d09a4314d40869acc6Maurice Lam super(context); 629f9367672191190f903955d09a4314d40869acc6Maurice Lam init(null, android.R.attr.listViewStyle); 63efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 64efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 65efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public StickyHeaderListView(Context context, AttributeSet attrs) { 669f9367672191190f903955d09a4314d40869acc6Maurice Lam super(context, attrs); 679f9367672191190f903955d09a4314d40869acc6Maurice Lam init(attrs, android.R.attr.listViewStyle); 68efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 69efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 70efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public StickyHeaderListView(Context context, AttributeSet attrs, int defStyleAttr) { 719f9367672191190f903955d09a4314d40869acc6Maurice Lam super(context, attrs, defStyleAttr); 729f9367672191190f903955d09a4314d40869acc6Maurice Lam init(attrs, defStyleAttr); 73efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 74efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 759f9367672191190f903955d09a4314d40869acc6Maurice Lam private void init(AttributeSet attrs, int defStyleAttr) { 769f9367672191190f903955d09a4314d40869acc6Maurice Lam final TypedArray a = getContext().obtainStyledAttributes(attrs, 779f9367672191190f903955d09a4314d40869acc6Maurice Lam R.styleable.SuwStickyHeaderListView, defStyleAttr, 0); 7895af12dac219e6945bab6142ea3a4099077314aaMaurice Lam int headerResId = a.getResourceId(R.styleable.SuwStickyHeaderListView_suwHeader, 0); 7995af12dac219e6945bab6142ea3a4099077314aaMaurice Lam if (headerResId != 0) { 809f9367672191190f903955d09a4314d40869acc6Maurice Lam LayoutInflater inflater = LayoutInflater.from(getContext()); 8195af12dac219e6945bab6142ea3a4099077314aaMaurice Lam View header = inflater.inflate(headerResId, this, false); 820c0a00a3fbf90e5c80fd67dc002b6a680b7744acUdam Saini addHeaderView(header, null, false); 83efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 8495af12dac219e6945bab6142ea3a4099077314aaMaurice Lam a.recycle(); 85efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 86efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 87efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam @Override 88efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam protected void onLayout(boolean changed, int l, int t, int r, int b) { 89efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam super.onLayout(changed, l, t, r, b); 90efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (mSticky == null) { 91efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam updateStickyView(); 92efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 93efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 94efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 95efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public void updateStickyView() { 96efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mSticky = findViewWithTag("sticky"); 97efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mStickyContainer = findViewWithTag("stickyContainer"); 98efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 99efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 100efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam @Override 101efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public boolean dispatchTouchEvent(MotionEvent ev) { 102efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (mStickyRect.contains(ev.getX(), ev.getY())) { 103efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam ev.offsetLocation(-mStickyRect.left, -mStickyRect.top); 104efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam return mStickyContainer.dispatchTouchEvent(ev); 105efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } else { 106efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam return super.dispatchTouchEvent(ev); 107efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 108efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 109efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 110efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam @Override 111efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public void draw(Canvas canvas) { 112efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam super.draw(canvas); 113efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (mSticky != null) { 114efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam final int saveCount = canvas.save(); 115efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam // The view to draw when sticking to the top 116efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky; 117efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam // The offset to draw the view at when sticky 118efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0; 119efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam // Position of the draw target, relative to the outside of the scrollView 120efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam final int drawTop = drawTarget.getTop(); 121efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) { 122efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam // ListView does not translate the canvas, so we can simply draw at the top 123efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(), 124efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam drawTarget.getHeight() - drawOffset + mStatusBarInset); 125efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam canvas.translate(0, mStickyRect.top); 126efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight()); 127efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam drawTarget.draw(canvas); 128efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } else { 129efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mStickyRect.setEmpty(); 130efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 131efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam canvas.restoreToCount(saveCount); 132efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 133efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 134efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 135efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam @Override 136efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam @TargetApi(Build.VERSION_CODES.LOLLIPOP) 137efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public WindowInsets onApplyWindowInsets(WindowInsets insets) { 138efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (getFitsSystemWindows()) { 139efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mStatusBarInset = insets.getSystemWindowInsetTop(); 140efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam insets.replaceSystemWindowInsets( 141efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam insets.getSystemWindowInsetLeft(), 142efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 0, /* top */ 143efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam insets.getSystemWindowInsetRight(), 144efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam insets.getSystemWindowInsetBottom() 145efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam ); 146efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 147efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam return insets; 148efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 1492f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam 1502f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam @Override 1512f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1522f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam super.onInitializeAccessibilityEvent(event); 1532f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam 1542f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam // Decoration-only headers should not count as an item for accessibility, adjust the 1552f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam // accessibility event to account for that. 1562f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam final int numberOfHeaders = mSticky != null ? 1 : 0; 1572f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam event.setItemCount(event.getItemCount() - numberOfHeaders); 1582f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam event.setFromIndex(Math.max(event.getFromIndex() - numberOfHeaders, 0)); 1592f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 1602f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam event.setToIndex(Math.max(event.getToIndex() - numberOfHeaders, 0)); 1612f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam } 1622f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam } 163efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam} 164