25 | * 因为热门城市和全部城市,一般不会改变,所以将其设置为静态的 26 | *
27 | */ 28 | private static List215 | * 所有城市缩写的第一个字母[A, B, C, D, F, G, H, J, K, L, M, N, P, Q, R, S, T, W, X, Y, Z] 216 | *
217 | * @return 218 | */ 219 | private TreeSet240 | * {"当前", "热门", "A", "B", "C", "D", "F", "G", "H", "J", "K", 241 | * "L", "M", "N", "O", "P", "Q", "R", "S", "T", "W", "X", 242 | * "Y", "Z"}; 243 | *
244 | */ 245 | public String[] sectionsAndBlade(String... presentLocation){ 246 | mPreCharNum = presentLocation.length ; 247 | String [] sectionsAndBlade ; 248 | sectionsAndBlade = ArrayUtils.addAll( 249 | presentLocation, 250 | mFirstCharFromAbbreviationOfCity.toArray(new String[mFirstCharFromAbbreviationOfCity.size()])) ; 251 | for (String string : sectionsAndBlade) { 252 | Log.d(TAG,string) ; 253 | } 254 | return sectionsAndBlade ; 255 | } 256 | // public static final String ALL_CHARACTER = "#ABCDFGHJKLMNOPQRSTWXYZ"; 257 | public String allCharacter(){ 258 | StringBuilder stringBuilder = new StringBuilder() ; 259 | for (int i = 0; i < mPreCharNum; i++) { 260 | stringBuilder.append("#") ; 261 | } 262 | for (String s : mFirstCharFromAbbreviationOfCity) { 263 | stringBuilder.append(s) ; 264 | } 265 | return stringBuilder.toString() ; 266 | 267 | } 268 | } -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/db/DBHelper.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo.db; 2 | 3 | import android.database.sqlite.SQLiteDatabase; 4 | 5 | import java.util.concurrent.locks.Lock; 6 | import java.util.concurrent.locks.ReadWriteLock; 7 | import java.util.concurrent.locks.ReentrantReadWriteLock; 8 | 9 | public class DBHelper { 10 | 11 | private static final String TAG = "DBHelper" ; 12 | 13 | /** 14 | * 数据库相关的所有表 15 | */ 16 | public interface TableData{ 17 | /** 18 | * 城市表 19 | */ 20 | public interface CityTable{ 21 | String table = "city" ; 22 | String id ="id" ; 23 | String name ="name" ; 24 | String pyf ="pyf" ; 25 | String pys ="pys" ; 26 | String hot ="hot" ; 27 | } 28 | } 29 | 30 | private ReadWriteLock lock = new ReentrantReadWriteLock(true); 31 | private Lock readLock = lock.readLock(); 32 | private Lock writeLock = lock.writeLock(); 33 | 34 | /** 35 | * 从指定数据库文件获取一个可读的 SQLiteDatabase 36 | * @param dbDirPath 37 | * @param dbFileName 38 | * @return 39 | */ 40 | public SQLiteDatabase getReadableDataBase(String dbDirPath, String dbFileName) { 41 | 42 | readLock.lock(); 43 | 44 | try { 45 | String dbPath = dbDirPath.concat(dbFileName); 46 | 47 | return SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS); 48 | } finally { 49 | readLock.unlock(); 50 | } 51 | } 52 | 53 | /** 54 | * 从指定数据库文件获取一个以读写的 SQLiteDatabase 55 | * @param dbDirPath 56 | * @param dbFileName 57 | * @return 58 | */ 59 | public SQLiteDatabase getWritableDataBase(String dbDirPath, String dbFileName) { 60 | 61 | writeLock.lock(); 62 | 63 | try { 64 | String dbPath = dbDirPath.concat(dbFileName); 65 | 66 | return SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS); 67 | } finally { 68 | writeLock.unlock(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/view/BladeView.java: -------------------------------------------------------------------------------- 1 | package com.example.pinnedheaderlistviewdemo.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.os.Handler; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.Gravity; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.widget.PopupWindow; 14 | import android.widget.TextView; 15 | import com.example.pinnedheaderlistviewdemo.MainActivity; 16 | import com.example.pinnedheaderlistviewdemo.R; 17 | import com.example.pinnedheaderlistviewdemo.db.CityDao; 18 | import com.example.pinnedheaderlistviewdemo.db.DBHelper; 19 | 20 | /** 21 | * 竖向选择器 字母索引 22 | */ 23 | public class BladeView extends View { 24 | private static final String TAG ="BladeView" ; 25 | 26 | private OnItemClickListener mOnItemClickListener; 27 | private static String[] mBlade ; 28 | // = {"当前", "A", "B", "C", "D", "F", "G", "H", "J", "K", 29 | // "L", "M", "N", "O", "P", "Q", "R", "S", "T", "W", "X", 30 | // "Y", "Z"}; 31 | int choose = -1; 32 | Paint paint = new Paint(); 33 | boolean showBkg = false; 34 | private PopupWindow mPopupWindow; 35 | private TextView mPopupText; 36 | private Handler handler = new Handler(); 37 | 38 | { 39 | mBlade = new CityDao(new DBHelper()).sectionsAndBlade("当前"); 40 | } 41 | public BladeView(Context context, AttributeSet attrs, int defStyle) { 42 | super(context, attrs, defStyle); 43 | } 44 | 45 | public BladeView(Context context, AttributeSet attrs) { 46 | super(context, attrs); 47 | } 48 | 49 | public BladeView(Context context) { 50 | super(context); 51 | } 52 | 53 | @Override 54 | protected void onDraw(Canvas canvas) { 55 | super.onDraw(canvas); 56 | if (showBkg) { 57 | canvas.drawColor(Color.parseColor("#AAAAAA")); 58 | } 59 | 60 | int height = getHeight(); 61 | int width = getWidth(); 62 | int singleHeight = height / mBlade.length; 63 | for (int i = 0; i < mBlade.length; i++) { 64 | //竖向选择器中字母默认显示的颜色 65 | paint.setColor(Color.parseColor("#ff2f2f2f")); 66 | // paint.setTypeface(Typeface.DEFAULT_BOLD); //加粗 67 | paint.setTextSize(getResources().getDimensionPixelSize(R.dimen.bladeview_fontsize));//设置字体的大小 68 | paint.setFakeBoldText(true); 69 | paint.setAntiAlias(true); 70 | if (i == choose) { 71 | //竖向选择器中 被选中字母的颜色 72 | paint.setColor(Color.parseColor("#ff0000")); 73 | // paint.setColor(Color.parseColor("#3399ff")); 74 | } 75 | float xPos = width / 2 - paint.measureText(mBlade[i]) / 2; 76 | float yPos = singleHeight * i + singleHeight; 77 | canvas.drawText(mBlade[i], xPos, yPos, paint); 78 | paint.reset(); 79 | } 80 | 81 | } 82 | 83 | @Override 84 | public boolean dispatchTouchEvent(MotionEvent event) { 85 | final int action = event.getAction(); 86 | final float y = event.getY(); 87 | final int oldChoose = choose; 88 | //用户点击选中的字母 89 | final int choiceLetter = (int) (y / getHeight() * mBlade.length); 90 | 91 | switch (action) { 92 | case MotionEvent.ACTION_DOWN: 93 | //如果此处设置为true,意为当竖向选择器被点击(包括滑动状态)时,显示背景颜色 94 | // showBkg = true; 95 | if (oldChoose != choiceLetter) { 96 | if (choiceLetter >= 0 && choiceLetter < mBlade.length) { //让第一个字母响应点击事件 97 | performItemClicked(choiceLetter); 98 | choose = choiceLetter; 99 | invalidate(); 100 | } 101 | } 102 | 103 | break; 104 | case MotionEvent.ACTION_MOVE: 105 | if (oldChoose != choiceLetter) { 106 | if (choiceLetter >= 0 && choiceLetter < mBlade.length) { //让第一个字母响应点击事件 107 | performItemClicked(choiceLetter); 108 | choose = choiceLetter; 109 | /* 110 | invalidate: 111 | Invalidate[ɪn'vælɪdeɪt] the whole view. If the view is visible, 112 | onDraw(android.graphics.Canvas) will be called at some point in 113 | the future. This must be called from a UI thread. To call from a non-UI thread, 114 | call postInvalidate(). 115 | */ 116 | invalidate(); 117 | } 118 | } 119 | break; 120 | case MotionEvent.ACTION_UP: 121 | showBkg = false; 122 | choose = -1; 123 | dismissPopup(); 124 | invalidate(); 125 | break; 126 | } 127 | return true; 128 | } 129 | 130 | /** 131 | * 弹出显示 字母索引 132 | * @param item 133 | */ 134 | private void showPopup(int item) { 135 | if (mPopupWindow == null) { 136 | 137 | handler.removeCallbacks(dismissRunnable); 138 | mPopupText = new TextView(getContext()); 139 | mPopupText.setBackgroundColor(Color.GRAY); 140 | mPopupText.setTextColor(Color.WHITE); 141 | mPopupText.setTextSize(getResources().getDimensionPixelSize(R.dimen.bladeview_popup_fontsize)); 142 | mPopupText.setGravity(Gravity.CENTER_HORIZONTAL 143 | | Gravity.CENTER_VERTICAL); 144 | 145 | int height = getResources().getDimensionPixelSize(R.dimen.bladeview_popup_height); 146 | 147 | mPopupWindow = new PopupWindow(mPopupText, height, height); 148 | } 149 | 150 | String text = ""; 151 | if (item == 0) { 152 | text = "当前"; 153 | } else { 154 | text = MainActivity.ALL_CHARACTER.substring(item, item + 1) ; 155 | // text = Character.toString((char) ('A' + item - 1)); 156 | } 157 | mPopupText.setText(text); 158 | if (mPopupWindow.isShowing()) { 159 | mPopupWindow.update(); 160 | } else { 161 | mPopupWindow.showAtLocation(getRootView(), 162 | Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL, 0, 0); 163 | } 164 | } 165 | 166 | private void dismissPopup() { 167 | handler.postDelayed(dismissRunnable, 800); 168 | } 169 | 170 | Runnable dismissRunnable = new Runnable() { 171 | 172 | @Override 173 | public void run() { 174 | // TODO Auto-generated method stub 175 | if (mPopupWindow != null) { 176 | mPopupWindow.dismiss(); 177 | } 178 | } 179 | }; 180 | 181 | public boolean onTouchEvent(MotionEvent event) { 182 | return super.onTouchEvent(event); 183 | } 184 | 185 | public void setOnItemClickListener(OnItemClickListener listener) { 186 | mOnItemClickListener = listener; 187 | } 188 | 189 | private void performItemClicked(int item) { 190 | if (mOnItemClickListener != null) { 191 | Log.d(TAG,"item :" + item + "对应的字母为" + mBlade[item]) ; 192 | mOnItemClickListener.onItemClick(mBlade[item]); 193 | showPopup(item); 194 | } 195 | } 196 | 197 | public interface OnItemClickListener { 198 | void onItemClick(String s); 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/com/example/pinnedheaderlistviewdemo/view/PinnedHeaderListView.java: -------------------------------------------------------------------------------- 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.example.pinnedheaderlistviewdemo.view; 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 | --------------------------------------------------------------------------------