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