PinnedHeaderListView.java revision 1a848b1f6ab34d9cfe90ed13f20bb9b5131246d0
1/* 2 * Copyright (C) 2010 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.contacts.widget; 18 19import android.content.Context; 20import android.graphics.Canvas; 21import android.util.AttributeSet; 22import android.view.View; 23import android.view.ViewGroup; 24import android.widget.AbsListView; 25import android.widget.ListAdapter; 26import android.widget.ListView; 27import android.widget.AbsListView.OnScrollListener; 28 29/** 30 * A ListView that maintains a header pinned at the top of the list. The 31 * pinned header can be pushed up and dissolved as needed. 32 */ 33public class PinnedHeaderListView extends ListView implements OnScrollListener { 34 35 /** 36 * Adapter interface. The list adapter must implement this interface. 37 */ 38 public interface PinnedHeaderAdapter { 39 40 /** 41 * Pinned header state: don't show the header. 42 */ 43 public static final int PINNED_HEADER_GONE = 0; 44 45 /** 46 * Pinned header state: show the header at the top of the list. 47 */ 48 public static final int PINNED_HEADER_VISIBLE = 1; 49 50 /** 51 * Pinned header state: show the header. If the header extends beyond 52 * the bottom of the first shown element, push it up and clip. 53 */ 54 public static final int PINNED_HEADER_PUSHED_UP = 2; 55 56 /** 57 * Creates the pinned header view. 58 */ 59 View createPinnedHeaderView(ViewGroup parent); 60 61 /** 62 * Computes the desired state of the pinned header for the given 63 * position of the first visible list item. Allowed return values are 64 * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or 65 * {@link #PINNED_HEADER_PUSHED_UP}. 66 */ 67 int getPinnedHeaderState(int position); 68 69 /** 70 * Configures the pinned header view to match the first visible list item. 71 * 72 * @param header pinned header view. 73 * @param position position of the first visible list item. 74 * @param alpha fading of the header view, between 0 and 255. 75 */ 76 void configurePinnedHeader(View header, int position, int alpha); 77 } 78 79 private static final int MAX_ALPHA = 255; 80 81 private PinnedHeaderAdapter mAdapter; 82 private View mHeaderView; 83 private boolean mHeaderViewVisible; 84 private int mHeaderViewWidth; 85 private int mHeaderViewHeight; 86 87 private OnScrollListener mOnScrollListener; 88 89 public PinnedHeaderListView(Context context) { 90 super(context); 91 } 92 93 public PinnedHeaderListView(Context context, AttributeSet attrs) { 94 super(context, attrs); 95 super.setOnScrollListener(this); 96 } 97 98 public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) { 99 super(context, attrs, defStyle); 100 super.setOnScrollListener(this); 101 } 102 103 public void setPinnedHeaderView(View view) { 104 mHeaderView = view; 105 106 // Disable vertical fading when the pinned header is present 107 // TODO change ListView to allow separate measures for top and bottom fading edge; 108 // in this particular case we would like to disable the top, but not the bottom edge. 109 if (mHeaderView != null) { 110 setFadingEdgeLength(0); 111 } 112 requestLayout(); 113 } 114 115 @Override 116 public void setAdapter(ListAdapter adapter) { 117 super.setAdapter(adapter); 118 mAdapter = (PinnedHeaderAdapter)adapter; 119 View headerView = mAdapter.createPinnedHeaderView(this); 120 setPinnedHeaderView(headerView); 121 } 122 123 @Override 124 public void setOnScrollListener(OnScrollListener onScrollListener) { 125 mOnScrollListener = onScrollListener; 126 super.setOnScrollListener(this); 127 } 128 129 @Override 130 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 131 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 132 if (mHeaderView != null) { 133 configureHeaderView(getFirstVisiblePosition() - getHeaderViewsCount()); 134 measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); 135 mHeaderViewWidth = mHeaderView.getMeasuredWidth(); 136 mHeaderViewHeight = mHeaderView.getMeasuredHeight(); 137 } 138 } 139 140 @Override 141 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 142 super.onLayout(changed, left, top, right, bottom); 143 if (mHeaderView != null) { 144 mHeaderView.layout(0, mHeaderView.getTop(), mHeaderViewWidth, mHeaderViewHeight); 145 } 146 } 147 148 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 149 int totalItemCount) { 150 configureHeaderView(firstVisibleItem - getHeaderViewsCount()); 151 if (mOnScrollListener != null) { 152 mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount); 153 } 154 } 155 156 public void onScrollStateChanged(AbsListView view, int scrollState) { 157 if (mOnScrollListener != null) { 158 mOnScrollListener.onScrollStateChanged(this, scrollState); 159 } 160 } 161 162 public void configureHeaderView(int position) { 163 if (mHeaderView == null) { 164 return; 165 } 166 167 int state = mAdapter.getPinnedHeaderState(position); 168 switch (state) { 169 case PinnedHeaderAdapter.PINNED_HEADER_GONE: { 170 mHeaderViewVisible = false; 171 break; 172 } 173 174 case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: { 175 mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA); 176 if (mHeaderView.getTop() != 0) { 177 mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); 178 } 179 mHeaderViewVisible = true; 180 break; 181 } 182 183 case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: { 184 View firstView = getChildAt(0); 185 int bottom = firstView.getBottom(); 186 int headerHeight = mHeaderViewHeight; 187 int y; 188 int alpha; 189 if (bottom < headerHeight) { 190 y = (bottom - headerHeight); 191 alpha = MAX_ALPHA * (headerHeight + y) / headerHeight; 192 } else { 193 y = 0; 194 alpha = MAX_ALPHA; 195 } 196 mAdapter.configurePinnedHeader(mHeaderView, position, alpha); 197 if (mHeaderView.getTop() != y) { 198 mHeaderView.layout(0, y, headerHeight, headerHeight + y); 199 } 200 mHeaderViewVisible = true; 201 break; 202 } 203 } 204 } 205 206 @Override 207 protected void dispatchDraw(Canvas canvas) { 208 super.dispatchDraw(canvas); 209 if (mHeaderViewVisible) { 210 drawChild(canvas, mHeaderView, getDrawingTime()); 211 } 212 } 213} 214