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 package com.android.contacts.widget; 17 18 import com.android.common.widget.CompositeCursorAdapter; 19 20 import android.content.Context; 21 import android.view.View; 22 import android.view.ViewGroup; 23 24 /** 25 * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers. 26 */ 27 public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter 28 implements PinnedHeaderListView.PinnedHeaderAdapter { 29 30 public static final int PARTITION_HEADER_TYPE = 0; 31 32 private boolean mPinnedPartitionHeadersEnabled; 33 private boolean mHeaderVisibility[]; 34 PinnedHeaderListAdapter(Context context)35 public PinnedHeaderListAdapter(Context context) { 36 super(context); 37 } 38 PinnedHeaderListAdapter(Context context, int initialCapacity)39 public PinnedHeaderListAdapter(Context context, int initialCapacity) { 40 super(context, initialCapacity); 41 } 42 getPinnedPartitionHeadersEnabled()43 public boolean getPinnedPartitionHeadersEnabled() { 44 return mPinnedPartitionHeadersEnabled; 45 } 46 setPinnedPartitionHeadersEnabled(boolean flag)47 public void setPinnedPartitionHeadersEnabled(boolean flag) { 48 this.mPinnedPartitionHeadersEnabled = flag; 49 } 50 51 @Override getPinnedHeaderCount()52 public int getPinnedHeaderCount() { 53 if (mPinnedPartitionHeadersEnabled) { 54 return getPartitionCount(); 55 } else { 56 return 0; 57 } 58 } 59 isPinnedPartitionHeaderVisible(int partition)60 protected boolean isPinnedPartitionHeaderVisible(int partition) { 61 return mPinnedPartitionHeadersEnabled && hasHeader(partition) 62 && !isPartitionEmpty(partition); 63 } 64 65 /** 66 * The default implementation creates the same type of view as a normal 67 * partition header. 68 */ 69 @Override getPinnedHeaderView(int partition, View convertView, ViewGroup parent)70 public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) { 71 if (hasHeader(partition)) { 72 View view = null; 73 if (convertView != null) { 74 Integer headerType = (Integer)convertView.getTag(); 75 if (headerType != null && headerType == PARTITION_HEADER_TYPE) { 76 view = convertView; 77 } 78 } 79 if (view == null) { 80 view = newHeaderView(getContext(), partition, null, parent); 81 view.setTag(PARTITION_HEADER_TYPE); 82 view.setFocusable(false); 83 view.setEnabled(false); 84 } 85 bindHeaderView(view, partition, getCursor(partition)); 86 return view; 87 } else { 88 return null; 89 } 90 } 91 92 @Override configurePinnedHeaders(PinnedHeaderListView listView)93 public void configurePinnedHeaders(PinnedHeaderListView listView) { 94 if (!mPinnedPartitionHeadersEnabled) { 95 return; 96 } 97 98 int size = getPartitionCount(); 99 100 // Cache visibility bits, because we will need them several times later on 101 if (mHeaderVisibility == null || mHeaderVisibility.length != size) { 102 mHeaderVisibility = new boolean[size]; 103 } 104 for (int i = 0; i < size; i++) { 105 boolean visible = isPinnedPartitionHeaderVisible(i); 106 mHeaderVisibility[i] = visible; 107 if (!visible) { 108 listView.setHeaderInvisible(i, true); 109 } 110 } 111 112 int headerViewsCount = listView.getHeaderViewsCount(); 113 114 // Starting at the top, find and pin headers for partitions preceding the visible one(s) 115 int maxTopHeader = -1; 116 int topHeaderHeight = 0; 117 for (int i = 0; i < size; i++) { 118 if (mHeaderVisibility[i]) { 119 int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount; 120 int partition = getPartitionForPosition(position); 121 if (i > partition) { 122 break; 123 } 124 125 listView.setHeaderPinnedAtTop(i, topHeaderHeight, false); 126 topHeaderHeight += listView.getPinnedHeaderHeight(i); 127 maxTopHeader = i; 128 } 129 } 130 131 // Starting at the bottom, find and pin headers for partitions following the visible one(s) 132 int maxBottomHeader = size; 133 int bottomHeaderHeight = 0; 134 int listHeight = listView.getHeight(); 135 for (int i = size; --i > maxTopHeader;) { 136 if (mHeaderVisibility[i]) { 137 int position = listView.getPositionAt(listHeight - bottomHeaderHeight) 138 - headerViewsCount; 139 if (position < 0) { 140 break; 141 } 142 143 int partition = getPartitionForPosition(position - 1); 144 if (partition == -1 || i <= partition) { 145 break; 146 } 147 148 int height = listView.getPinnedHeaderHeight(i); 149 bottomHeaderHeight += height; 150 // Animate the header only if the partition is completely invisible below 151 // the bottom of the view 152 int firstPositionForPartition = getPositionForPartition(i); 153 boolean animate = position < firstPositionForPartition; 154 listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, animate); 155 maxBottomHeader = i; 156 } 157 } 158 159 // Headers in between the top-pinned and bottom-pinned should be hidden 160 for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) { 161 if (mHeaderVisibility[i]) { 162 listView.setHeaderInvisible(i, isPartitionEmpty(i)); 163 } 164 } 165 } 166 167 @Override 168 public int getScrollPositionForHeader(int viewIndex) { 169 return getPositionForPartition(viewIndex); 170 } 171 } 172