类 微信 通讯录,实时搜索,首字母分类,滚动header push,以及右侧字母列表

第一次写博客。。写的太渣请见谅。可以直接下代码~~

这段时间的项目中用到了这么一个类似的功能,主要用来选择城市。

刚好现在有空,所以提取整理出了代码。

当时在网上找了大概3-4个类似demo,但都功能不完整。所以决定参照那些demo自己写一个。


效果图



代码结构



先把思路说下。在每一个item上我都有一个head 和一个content,在adapter里判断当前item 的首字母是否首次出现,如果是首次出现,那么就显示head。
而那个可以被往上推动的headerView布局和  item 中 head的布局是一模一样的。在listView 滚动的时候, 不断判断下一个首字母的position 是否是当前item首字母的position+1,如果是的话,那么就要往上推动了。不是的话悬浮在顶部不动。
那么是如何判断这两个position的呢,就要用到 AlphaChar这个对象了,里面有两个字段一个是首字母char ,一个是index 首字母的position。通过遍历获得下一个首字母的位置
Iterator<AlphaChar> iterator=alphaChars.iterator();
		while (iterator.hasNext()) {
			AlphaChar ac= iterator.next();
			if (ac.c==section &&iterator.hasNext()) {
				ac=iterator.next();
				return ac.index;
			}
		}
		return -1;

先把PinnedHeaderListView介绍一下。这个View 继承了ListView ,并且实现了OnScrollListener接口。
public class PinnedHeaderListView extends ListView implements OnScrollListener{
	private static final String TAG = "";

	public static final int PINNED_HEADER_GONE = 0;
	public static final int PINNED_HEADER_VISIBLE = 1;
	public static final int PINNED_HEADER_PUSHED_UP = 2;
	
	private static final int MAX_ALPHA = 255;

	private CityAdapter mAdapter;
	private View mHeaderView;
	private boolean mHeaderViewVisible;
	private int mHeaderViewWidth;
	private int mHeaderViewHeight;

	public PinnedHeaderListView(Context context) {
		super(context);
	}

	public PinnedHeaderListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public PinnedHeaderListView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		if (isInEditMode()) {
			return;
		}
		
	}
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mHeaderView != null) {
            mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
            configureHeaderView(getFirstVisiblePosition());
        }
    }
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHeaderView != null) {
            measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
            mHeaderViewWidth = mHeaderView.getMeasuredWidth();
            mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        }
    }
	public void setPinnedHeaderView(View view) {
		mHeaderView = view;
		if (mHeaderView != null) {
			setFadingEdgeLength(0);
		}
		requestLayout();
		this.setOnScrollListener(this);
	}
    public void setAdapter(CityAdapter adapter) {
        super.setAdapter(adapter);
        
			mAdapter=adapter;
		
    }
    /**
     * 根据state状态 修改headView 的动作
     * @param position
     */
	public void configureHeaderView(int position) {
		if (mHeaderView == null) {
			return;
		}
		int state = getPinnedHeaderState(position);
		
		switch (state) {
		case PINNED_HEADER_GONE: {
			mHeaderViewVisible = false;
			break;
		}

		case PINNED_HEADER_VISIBLE: {
			configurePinnedHeader(position);
			if (mHeaderView.getTop() != 0) {
				mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
			}
			mHeaderViewVisible = true;
			break;
		}

		case PINNED_HEADER_PUSHED_UP: {
			View firstView = getChildAt(0);
			if(firstView != null){
				
				int bottom = firstView.getBottom();
				int headerHeight = mHeaderView.getHeight();
				int y;
				int alpha;
				if (bottom < headerHeight) {
					y = (bottom - headerHeight);
					alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
				} else {
					y = 0;
					alpha = MAX_ALPHA;
				}
				configurePinnedHeader(position);
				if (mHeaderView.getTop() != y) {
					
					mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight
							+ y);
				}
				mHeaderViewVisible = true;
			}else{
				Log.w(TAG, "firstView ==null");
			}
			break;
		}
		}
	}
	/**
	 * 判断当前是该隐藏还是悬浮 还是被推动上移
	 * @param position
	 * @return
	 */
	public int getPinnedHeaderState(int position) {
		int realPosition = position;
		if (realPosition < 0) {
			return PINNED_HEADER_GONE;
		}

		int section = mAdapter.getSectionForPosition(realPosition);
		int nextSectionPosition = mAdapter.getNextPositionForSection(section);
		if (nextSectionPosition != -1
				&& realPosition == nextSectionPosition - 1) {
			return PINNED_HEADER_PUSHED_UP;
		}
		return PINNED_HEADER_VISIBLE;
	}
	/**
	 * 设置head的文字
	 * @param position
	 */
	private void configurePinnedHeader(int position){
		
		char title=(char) mAdapter.getSectionForPosition(position);
		String text=String.valueOf(title);

		((TextView)mHeaderView.findViewById(R.id.query_ticket_header_text)).setText(text);
	}
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mHeaderViewVisible) {
            drawChild(canvas, mHeaderView, getDrawingTime());
        }
    }

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		// TODO Auto-generated method stub
		configureHeaderView(firstVisibleItem);
	}
}
CityAdapter 实现了SectionIndexer接口。代码里注释还好,就不多说其他的了,直接贴代码
public class CityAdapter extends BaseAdapter implements SectionIndexer{

	private static final String TAG = "TicketCityAdapter";

	private List<TicketCity> list;
	private LayoutInflater inflater;

	private List<AlphaChar> alphaChars;
	
	public CityAdapter(Context context, List<TicketCity> list,List<AlphaChar> alphaChars) {
		// TODO Auto-generated constructor stub
		inflater = LayoutInflater.from(context);
		this.list = list;
		this.alphaChars=alphaChars;

	}

	public void updateListView(List<TicketCity> list,List<AlphaChar> alphaChars) {
		this.list = list;
		this.alphaChars=alphaChars;
		
		notifyDataSetChanged();
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}

	@Override
	public TicketCity getItem(int position) {
		// TODO Auto-generated method stub
		return list.get(position);
	}

	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		int section = getSectionForPosition(position);
		Holder holder = null;
		if (convertView == null) {
			convertView = inflater.inflate(R.layout.query_ticket_item, null);
			holder = new Holder();
			holder.mHeaderParent=(LinearLayout)convertView.findViewById(R.id.query_ticket_item_parent);
			holder.mHeaderText=(TextView)convertView.findViewById(R.id.query_ticket_item_header_text);
			holder.textView=(TextView)convertView.findViewById(R.id.query_ticket_item_text);
			convertView.setTag(holder);
		} else {
			holder = (Holder) convertView.getTag();
		}

		
		if (getPositionForSection(section) == position) {
			holder.mHeaderParent.setVisibility(View.VISIBLE);
			String text=list.get(position).cityShortPinyin.toUpperCase()
					.substring(0, 1);
			holder.mHeaderText.setText(text);
		} else {
			holder.mHeaderParent.setVisibility(View.GONE);
		}

		holder.textView.setText(list.get(position).cityName);
		return convertView;
	}

	/**
	 * 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置 section=65 在总list中查开头是65的
	 */
	@Override
	public int getPositionForSection(int section) {
		if (section < 0) {
			return -1;
		}
		for (AlphaChar ac : alphaChars) {
			if (ac.c==section) {
				return ac.index;
			}
		}
		return -1;
	}
	/**
	 * 获得下一个首字母的位置
	 * @param section
	 * @return
	 */
	public int getNextPositionForSection(int section) {
		if (section < 0 ) {
			return -1;
		}
		Iterator<AlphaChar> iterator=alphaChars.iterator();
		while (iterator.hasNext()) {
			AlphaChar ac= iterator.next();
			if (ac.c==section &&iterator.hasNext()) {
				ac=iterator.next();
				return ac.index;
			}
		}
		return -1;
	}
	/**
	 * 根据ListView的当前位置获取分类的首字母的Char ascii值 
	 * 例如cityShortPinyin=ags char= A return 65
	 */
	@Override
	public int getSectionForPosition(int position) {
		// TODO Auto-generated method stub
		if (position >= list.size()) {
			return -1;
		}
		return list.get(position).cityShortPinyin.toUpperCase().charAt(0);
	}

	@Override
	public Object[] getSections() {
		// TODO Auto-generated method stub
		return null;
	}

	class Holder {
		
		LinearLayout mHeaderParent;
		
		TextView mHeaderText;
		
		TextView textView;

	}


}

然后是 MainActivity
public class MainActivity extends Activity {
	private static final String TAG = MainActivity.class.getSimpleName();

	private static final int HANDLER_MSG_SHOW = 1;
	private PinnedHeaderListView mListView;

	private CityAdapter mAdapter;

	private BladeView mLetter;

	private ClearEditText mClearEditText;

	private List<TicketCity> tcs;

	private List<AlphaChar> alphaChars;
	String[] datas;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mListView = (PinnedHeaderListView) findViewById(R.id.query_ticket_activity_list);
		mLetter = (BladeView) findViewById(R.id.query_ticket_activity_sidrbar);
		mClearEditText = (ClearEditText) findViewById(R.id.query_ticket_activity_edit);
		init();
	}

	private void init() {
		Thread thread = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				// http获取数据等,这里直接读本地的文件了
				datas = getResources().getStringArray(R.array.countries);
				if (tcs==null) {
					tcs=new ArrayList<TicketCity>();
				}
				tcs.clear();
				TicketCity tc;
				for (int i = 0; i < datas.length; i++) {
					tc = new TicketCity();
					tc.cityShortPinyin = datas[i];
					tc.cityName = datas[i] + "名字" + i;
					tc.cityFullPinyin = datas[i] + "全拼" + i;
					tcs.add(tc);
				}
				Collections.sort(tcs);
				initSAlphaChar(tcs);
				mHandler.sendEmptyMessage(HANDLER_MSG_SHOW);
			}
		});
		thread.start();

	}

	private Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);
			switch (msg.what) {
			case HANDLER_MSG_SHOW:
				showView();
				break;

			default:
				break;
			}
		}

	};

	protected void showView() {
		// TODO Auto-generated method stub
		mLetter.setOnItemClickListener(new BladeView.OnItemClickListener() {

			@Override
			public void onItemClick(String s) {
				mListView.setSelection(mAdapter.getPositionForSection(s
						.toUpperCase().charAt(0)));
			}
		});

		mClearEditText.addTextChangedListener(new TextWatcher() {

			@Override
			public void onTextChanged(CharSequence s, int start, int before,
					int count) {
				try {
					filterData(s.toString());
				} catch (Exception e) {
				}
			}

			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
				// TODO Auto-generated method stub

			}

			@Override
			public void afterTextChanged(Editable s) {
				// TODO Auto-generated method stub

			}
		});

		mAdapter = new CityAdapter(this, tcs, alphaChars);

		mListView.setAdapter(mAdapter);
		mListView.setPinnedHeaderView(LayoutInflater.from(this).inflate(
				R.layout.query_ticket_header, mListView, false));
		mListView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub
				String py= mAdapter.getItem(position).cityShortPinyin;
				Toast.makeText(MainActivity.this, py, Toast.LENGTH_LONG).show();;
			}
			
		});
	}

	/**
	 * 根据输入框中的值来过滤数据并更新ListView
	 * 
	 * @param filterStr
	 */
	private void filterData(String filterStr) {

		List<TicketCity> filterDateList = new ArrayList<TicketCity>();

		if (TextUtils.isEmpty(filterStr)) {
			filterDateList = tcs;
		} else {
			filterDateList.clear();
			for (TicketCity tc : tcs) {
				// 简拼 全屏 汉字都匹配
				if (tc.cityShortPinyin.toUpperCase().indexOf(
						filterStr.toUpperCase()) >= 0
						|| tc.cityName.indexOf(filterStr) >= 0
						|| tc.cityFullPinyin.toUpperCase().indexOf(
								filterStr.toUpperCase()) >= 0) {
					filterDateList.add(tc);
				}

			}
		}

		// 根据a-z进行排序
		Collections.sort(filterDateList);
		initSAlphaChar(filterDateList);
		mAdapter.updateListView(filterDateList, alphaChars);

	}

	/**
	 * 初始化起始城市首字母列表
	 * 
	 * @param list
	 */
	private void initSAlphaChar(List<TicketCity> list) {

		if (alphaChars == null) {
			alphaChars = new ArrayList<AlphaChar>();
		}
		alphaChars.clear();

		char c;
		char oldc = 0;

		for (int i = 0; i < list.size(); i++) {

			c = list.get(i).cityShortPinyin.toUpperCase().charAt(0);

			if (c != oldc) {
				AlphaChar ac = new AlphaChar();
				ac.c = c;
				ac.index = i;
				alphaChars.add(ac);
			}
			oldc = c;
		}

	}

}

还有两个数据类就没啥好说的了
TicketCity 加了一个排序
public class TicketCity  implements Comparable<TicketCity>{
	/**
	 * 城市名称
	 */
	public String cityName;
	/**
	 * 城市拼音全拼
	 */
	public String cityFullPinyin;
	/**
	 * 城市拼音简拼
	 */
	public String cityShortPinyin;
	
	
	Comparator cmp = Collator.getInstance(java.util.Locale.CHINA);
	@Override
	public int compareTo(TicketCity another) {
		// TODO Auto-generated method stub
		
		
		return cmp.compare(this.cityShortPinyin, another.cityShortPinyin);
	}
	
}




xml的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/query_ticket_item_parent"
    android:layout_width="fill_parent"
    android:layout_height="@dimen/dimen_query_ticket_listView_item_head_height"
    android:background="#e8e8e8"
    android:orientation="horizontal">

        <TextView
            android:id="@+id/query_ticket_item_header_text"
            android:layout_width="match_parent"
            android:layout_height="fill_parent"
            android:layout_marginLeft="@dimen/dimen_query_ticket_listView_item_text_margin_left"
            android:gravity="left|center_vertical"
            android:textColor="#696969"
            android:textSize="@dimen/dimen_query_ticket_listView_item_head_text_size" />

    </LinearLayout>

    <TextView
        android:id="@+id/query_ticket_item_text"
        android:layout_width="fill_parent"
        android:layout_height="@dimen/dimen_query_ticket_listView_item_text_height"
        android:layout_marginLeft="@dimen/dimen_query_ticket_listView_item_text_margin_left"
        android:gravity="center_vertical"
        android:text="城市"
        android:textSize="@dimen/dimen_query_ticket_listView_item_text_size" />

</LinearLayout>
主界面布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="activity.MainActivity" >

    <RelativeLayout
        android:id="@+id/query_ticket_activity_title_bar"
        android:layout_width="match_parent"
        android:layout_height="46dp"
        android:layout_alignParentRight="true"
        android:background="#47abef" >

        <view.ClearEditText
            android:id="@+id/query_ticket_activity_edit"
            android:layout_width="@dimen/dimen_query_ticket_edittext_width"
            android:layout_height="@dimen/dimen_query_ticket_edittext_height"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@+id/query_ticket_activity_back"
            android:background="@drawable/ico_search_box"
            android:drawableLeft="@drawable/ico_search"
            android:drawablePadding="14dp"
            android:hint="城市、车站的中文或拼音"
            android:paddingLeft="@dimen/dimen_query_ticket_edittext_search_pading_left"
            android:singleLine="true"
            android:textColor="#c2c2c2"
            android:textColorHint="#c2c2c2"
            android:textSize="@dimen/dimen_query_ticket_edittext_text_size" />
    </RelativeLayout>

    <view.PinnedHeaderListView
        android:id="@+id/query_ticket_activity_list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_below="@+id/query_ticket_activity_title_bar"
        android:layout_gravity="center"
        android:cacheColorHint="#00000000"
        android:divider="#e8e8e8"
        android:dividerHeight="1px"
        android:fadingEdge="none" />

    <view.BladeView
        android:id="@+id/query_ticket_activity_sidrbar"
        android:layout_width="30.0dip"
        android:layout_height="fill_parent"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/query_ticket_activity_title_bar"
        android:layout_gravity="right" />

</RelativeLayout>




资源下载链接点击打开链接




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值