Home | History | Annotate | Download | only in contacts
      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          */
     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          */
     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 
     81     public PinnedHeaderListView(Context context) {
     82         super(context);
     83     }
     84 
     85     public PinnedHeaderListView(Context context, AttributeSet attrs) {
     86         super(context, attrs);
     87     }
     88 
     89     public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
     90         super(context, attrs, defStyle);
     91     }
     92 
     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
    106     public void setAdapter(ListAdapter adapter) {
    107         super.setAdapter(adapter);
    108         mAdapter = (PinnedHeaderAdapter)adapter;
    109     }
    110 
    111     @Override
    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
    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 
    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
    176     protected void dispatchDraw(Canvas canvas) {
    177         super.dispatchDraw(canvas);
    178         if (mHeaderViewVisible) {
    179             drawChild(canvas, mHeaderView, getDrawingTime());
    180         }
    181     }
    182 }
    183