• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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